Compare commits

...

258 Commits

Author SHA1 Message Date
caoqianming d29fcce935 fix: base user_exist完善 2026-01-23 16:18:17 +08:00
caoqianming a534bde086 feat: wmaterial根据current_merged查询3 2026-01-22 16:12:35 +08:00
caoqianming 63002f27c8 feat: wmaterial根据current_merged查询2 2026-01-22 16:00:59 +08:00
caoqianming 4bbae8b7df feat: wmaterial根据current_merged查询 2026-01-21 11:09:53 +08:00
caoqianming dc26c7cc46 feat: handoverb添加oinfo_json字段 2026-01-16 15:29:58 +08:00
caoqianming 0d80e182cd feat: base user增加has_perm筛选条件 2026-01-16 14:48:08 +08:00
caoqianming 2759114ede feat: base 升级后同步数据库 2026-01-16 14:42:42 +08:00
caoqianming 80f832aa85 feat: 光子添加工段数据统计 2026-01-16 14:01:13 +08:00
caoqianming 70e49eb27e fix: batchst支持返回source_near修复 2026-01-16 14:00:52 +08:00
caoqianming e99b2ecbbc fix: base complexquerymixin支持add_info_for_list 2026-01-16 14:00:11 +08:00
caoqianming 146e842642 feat: with_source_near筛选体现在swagger里 2026-01-15 16:47:15 +08:00
caoqianming 47b1887c4b feat: base 调整asgi导入以保证正常启动 2026-01-15 09:13:09 +08:00
caoqianming 1ffbe0cc44 feat: wpr 返回wpr_from_ 2026-01-13 15:02:52 +08:00
caoqianming 3e173f7a72 feat: base cquery支持add_info_for_list 2026-01-13 14:57:13 +08:00
caoqianming fce66da1d9 feat: wpr 添加筛选条件wpr_from 2026-01-13 14:15:16 +08:00
caoqianming feb8bd6770 feat: wpr_bxerp优化 2026-01-13 10:29:41 +08:00
caoqianming 43f5f11ca8 feat: 未有ftest的也触发单个统计 2026-01-13 09:05:19 +08:00
caoqianming d5ea72a021 feat: 交接记录子项需保证工段/车间一致2 2026-01-12 15:44:48 +08:00
caoqianming 143d9cb719 fix: base locked_get_or_create优化 2026-01-12 15:30:55 +08:00
caoqianming cf6633592a feat: 交接记录子项需保证工段/车间一致 2026-01-12 13:44:08 +08:00
caoqianming b39b0e7923 fix: mlog并发优化的bug 2026-01-12 13:27:29 +08:00
caoqianming 70563a6c02 feat: mlog 并发优化 2026-01-12 11:16:04 +08:00
caoqianming def22f6b18 feat: handover可以查看仅交接到车间的记录 2026-01-12 10:28:51 +08:00
caoqianming f9eee5a523 feat: handover_revert 并发优化 2026-01-12 10:21:15 +08:00
caoqianming 2ecaeadff7 feat: handover_submit 并发优化2 2026-01-09 16:59:53 +08:00
caoqianming 6eee0e1e53 feat: handover_submit 并发优化 2026-01-09 16:54:24 +08:00
caoqianming 3417515e72 feat: base 添加locked_get_or_create 2026-01-09 16:53:57 +08:00
caoqianming 43abcbaa48 feat: 查询-n批次从正则改用like以优化性能 2026-01-09 15:55:56 +08:00
caoqianming e2a92b6faa feat: 固定依赖包 2026-01-08 10:40:00 +08:00
caoqianming 02e3265133 feat: 升级依赖包 2026-01-08 09:59:39 +08:00
caoqianming 65cdeb0e7c Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2026-01-07 16:26:42 +08:00
caoqianming 238d1dd074 release: 3.0.2026010716 2026-01-07 16:26:42 +08:00
TianyangZhang f7a78431c5 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2026-01-07 16:03:19 +08:00
TianyangZhang 5b60318bfb feat: gx-人员请假申请与审批 2026-01-07 16:03:18 +08:00
caoqianming 0127e2a149 feat: get_shift需要报错 2026-01-07 14:19:43 +08:00
caoqianming f5b1b13a63 feat: 添加rem模块 2026-01-06 14:19:37 +08:00
caoqianming f7b09ab1df fix: wpr list annotate明确number指向 2026-01-06 09:22:38 +08:00
caoqianming b362fc3b89 feat: 捕获除0异常 2026-01-05 08:19:38 +08:00
caoqianming 8f791ac8de feat: 按需求修改光芯批次统计分析 2026-01-04 15:55:03 +08:00
caoqianming afa3b8b9ad Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2026-01-04 14:41:37 +08:00
caoqianming 3d6fcfac8a feat: 批次统计数据支持返回source_near 2026-01-04 14:41:36 +08:00
TianyangZhang db910f0804 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2026-01-04 14:16:43 +08:00
TianyangZhang 7dc7a78695 feat:光芯OA 审批系统新增报价单审核 2026-01-04 14:16:41 +08:00
caoqianming 952cdb1bc7 feat: get_batch_dag还是只返回直接前后级别 2026-01-04 13:55:09 +08:00
caoqianming 9ed78f8d32 feat: mlogbbpatch修改批次号 2026-01-04 11:10:54 +08:00
caoqianming 52ebac68a0 feat: 出入库记录返回子表部分信息 2026-01-04 10:10:26 +08:00
caoqianming 81770e89fa feat: 统一撤回和撤销的表述2 2025-12-30 14:34:34 +08:00
caoqianming 6ac7c020bd feat: 统一撤回和撤销的表述 2025-12-30 14:32:29 +08:00
caoqianming e385a558e9 feat: 车间库存检验支持撤回 2025-12-30 14:28:20 +08:00
caoqianming c37e71d60f feat: wpr添加material_name查询条件 2025-12-30 10:07:41 +08:00
caoqianming c3c7675ac5 feat: 优化mlog_submit 返工后产品放在本工段下2 2025-12-29 15:54:14 +08:00
caoqianming 29f4e2f76a feat: 优化mlog_submit 返工后产品放在本工段下 2025-12-29 15:09:45 +08:00
caoqianming ec13b8b166 fix: 正常交接支持new_wm且支持不合格品 2025-12-29 14:41:06 +08:00
caoqianming c56f908b42 feat: mlogbin qct 可依据fix选择 2025-12-26 17:00:00 +08:00
caoqianming a8ae8ee32a feat: base dept filter支持parent isnull查询 2025-12-26 14:52:54 +08:00
caoqianming 3f183583c8 release: 3.0.2025122514 2025-12-25 15:00:24 +08:00
caoqianming 09a7c64b3c feat: mlogbw patch权限 2025-12-25 14:29:22 +08:00
caoqianming aa07c041fb feat: 固定资产入库流程apply 2025-12-25 14:27:28 +08:00
caoqianming 29f4edccb8 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-12-25 14:25:41 +08:00
caoqianming ca67a1479f feat: resignation ticket存入employee_name 2025-12-25 14:25:40 +08:00
TianyangZhang 90cb2ed7f7 feat:新增人员交接单及其修改 人员交接时候反存校验 2025-12-25 09:43:58 +08:00
caoqianming 79e957bd83 feat: asm assetlogcreate 2025-12-24 16:30:36 +08:00
caoqianming 00f2918d17 feat: base 提交时可变动工单title 2025-12-24 15:42:46 +08:00
caoqianming f84ceaa95e feat: 车间库存支持传入count_all 2025-12-24 11:09:12 +08:00
caoqianming da1f587036 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-12-23 10:25:19 +08:00
caoqianming 55c94a83ce feat: wmaterial search时去除material__number 2025-12-23 10:25:18 +08:00
TianyangZhang aad329061b Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-12-22 14:27:58 +08:00
TianyangZhang 16a04b43f0 feat: base ticketmixin 修改 ”perform_update“ bug 2025-12-22 14:27:57 +08:00
caoqianming 4536c136bb feat: wmaterial添加mlog_date_start筛选条件2 2025-12-22 14:26:05 +08:00
caoqianming 7400202209 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-12-22 14:22:02 +08:00
caoqianming d7c3e92f9e feat: wmaterial添加mlog_date_start筛选条件 2025-12-22 14:22:01 +08:00
TianyangZhang a408b2db92 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-12-22 13:41:40 +08:00
TianyangZhang b250b9b078 feat: 新增hrm --人员交接表 2025-12-22 13:41:38 +08:00
caoqianming 725f7d1764 fix: repair swaggger显示问题 2025-12-19 14:13:32 +08:00
caoqianming 75c7f26448 feat: base userfilter获取归属于该部门及以下部门的人2 2025-12-19 14:13:10 +08:00
caoqianming dec8650af1 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-12-19 14:02:51 +08:00
caoqianming a81e851588 feat: asm初步接口 2025-12-19 14:02:16 +08:00
caoqianming 4996b0e4a3 feat: base userfilter获取归属于该部门及以下部门的人 2025-12-19 13:59:39 +08:00
TianyangZhang 79c8115445 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-12-19 13:30:54 +08:00
TianyangZhang 21424a61bd fix: 修改 ofm - vehicle 中的出发里程 2025-12-19 13:30:53 +08:00
caoqianming b5fc176cf2 feat: 玻纤添加mioitemw检验导入表 2025-12-19 09:13:49 +08:00
caoqianming ba521b1107 feat: 修改mioitemw test的导入逻辑 2025-12-19 08:53:33 +08:00
caoqianming 81d2cbce8c feat: 光子六车间批次生产合格率修改2 2025-12-18 14:52:58 +08:00
caoqianming 45f9f27cbe feat: 光子六车间批次生产合格率修改 2025-12-18 10:13:51 +08:00
caoqianming c440707612 feat: 取消handover_submit无用校验 2025-12-17 16:06:10 +08:00
caoqianming 5dfb903c4a fix: handoverserializer 关于new_wm的处理4 2025-12-17 15:59:14 +08:00
caoqianming 7fc995c7c9 fix: handoverserializer 关于new_wm的处理3 2025-12-17 15:47:25 +08:00
caoqianming 07134f32bb fix: handoverserializer 关于new_wm的处理2 2025-12-17 15:34:08 +08:00
caoqianming 041474411e fix: handoverserializer 关于new_wm的处理 2025-12-17 15:24:38 +08:00
caoqianming 07b2df1fd0 feat: 合批时校验批次是否已存在 2025-12-17 14:25:40 +08:00
caoqianming 57459cc7dc Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-12-17 14:11:00 +08:00
caoqianming ec7b0cd987 fix: handovermerge时new_state未定义 2025-12-17 14:10:59 +08:00
TianyangZhang 5711791a1f Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-12-17 09:58:31 +08:00
TianyangZhang 4b7e648b13 fix: 修改印章申请binging_seal 2025-12-17 09:58:30 +08:00
caoqianming 0c5034683c fix: update_mb_item时考虑检验的存在 2025-12-17 09:25:44 +08:00
caoqianming c35041f72b feat: routepack删除时做一下校验 2025-12-16 16:33:05 +08:00
caoqianming 71fe281a90 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-12-16 11:23:01 +08:00
caoqianming 54f4aed729 feat: 维修记录初步完成 2025-12-16 11:23:00 +08:00
TianyangZhang d3c5358d2c fix: hrm-用人需求修改标题模板 2025-12-16 10:35:13 +08:00
caoqianming 190f2f047d feat: base ticketmixin传入other_data 2025-12-16 09:20:46 +08:00
caoqianming 3dcb26f14a fix: 先调整asm以启动服务 2025-12-15 16:47:10 +08:00
caoqianming 01615b4b27 feat: base 模板字段改为textfield 2025-12-15 16:45:41 +08:00
caoqianming be4dfe85ee Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-12-15 15:20:59 +08:00
caoqianming a9aac76328 feat: 删除asm同步文件 2025-12-15 15:20:58 +08:00
TianyangZhang 16a513f331 feat:修改hrm/urls --EMPneed 2025-12-15 13:52:33 +08:00
TianyangZhang de0149dce4 feat: 新增模块固定资产 asm 2025-12-15 11:18:55 +08:00
caoqianming 1af6555e26 feat: 添加员工需求表 2025-12-12 16:57:12 +08:00
caoqianming 320da10e61 feat: route接口增加按product获取总图 2025-12-12 11:00:05 +08:00
caoqianming 7f34d45160 feat: base wf增加ticket_count接口添加分类 2025-12-12 10:24:34 +08:00
caoqianming 8c18bbf6ae feat: base wf增加ticket_count接口 2025-12-12 09:34:35 +08:00
caoqianming 636042e9c3 feat: validate_dag检查检查未到达的物料 2025-12-12 08:54:01 +08:00
caoqianming e607dfd51b feat: cal_x_task_count默认使用target_quantity2 2025-12-12 08:33:57 +08:00
caoqianming ddd75d408a feat: 自动生成物料的逻辑优化2 2025-12-11 17:03:35 +08:00
caoqianming 6584334d00 feat: 自动生成物料的逻辑优化 2025-12-11 16:56:11 +08:00
caoqianming d36edaffe1 feat: cal_x_task_count默认使用target_quantity 2025-12-11 16:49:59 +08:00
caoqianming 33f6c3982d feat: base wf 捕获expr is None的错误 2025-12-11 13:43:44 +08:00
caoqianming 984f875a6c fix: do_in bug3 2025-12-11 10:41:53 +08:00
caoqianming 8ee48f77fc fix: do_in bug 2025-12-11 08:40:48 +08:00
caoqianming 4bea3d973e fix: do_in bug 2025-12-10 15:12:24 +08:00
caoqianming 18c66eee7a feat: validate_dag增加传入参数2 2025-12-09 09:41:53 +08:00
caoqianming 77c7811476 feat: validate_dag增加传入参数 2025-12-09 08:26:44 +08:00
caoqianming d104ee90a4 feat: 改版交接支持拆合批2 2025-12-08 15:26:39 +08:00
caoqianming 09c3bec66a feat: 改版交接支持拆合批 2025-12-08 13:57:02 +08:00
caoqianming 75b2a420b7 feat: translate_eval_formula 打印报错信息 2025-12-03 16:16:58 +08:00
caoqianming 7e09b872cf feat: base 优化safe_get_or_create2 2025-12-03 15:34:02 +08:00
caoqianming 53a56ace1f feat: base 优化safe_get_or_create 2025-12-03 13:52:08 +08:00
caoqianming 9388eada7d feat: base 增加statedetailserializer可返回节点操作人员 2025-12-03 11:11:30 +08:00
caoqianming 81aa49d339 feat: 获取batchlog dag数据支持临近节点返回 2025-12-02 09:56:25 +08:00
caoqianming adee19eb5b fix: base ticketmixin先创建再handle 2025-12-01 16:12:29 +08:00
caoqianming 0519facc9e Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-12-01 15:03:56 +08:00
caoqianming fadf1e979d feat: base ticket create支持transition非必传2 2025-12-01 15:03:49 +08:00
caoqianming a78b3a98a0 feat: base ticket create支持transition非必传 2025-12-01 15:03:49 +08:00
caoqianming 45fc29e9fe feat: base ticket create支持transition传空 2025-12-01 15:03:49 +08:00
caoqianming c60424e946 release: 3.0.2025120109 2025-12-01 15:03:49 +08:00
caoqianming cad2a7e50a feat: 供应商审核通过后即可添加供应商 2025-12-01 15:03:49 +08:00
caoqianming 39066f2124 feat: 校验SupplierAudit供应商名称已存在 2025-12-01 15:03:49 +08:00
caoqianming 8211a33b58 feat: material list filter low_inm优化 2025-12-01 15:03:49 +08:00
caoqianming a51494aea9 feat: material list 如需获取库存数据需指定传参 2025-12-01 15:03:49 +08:00
caoqianming 2fcfae518d feat: 添加部分索引 2025-12-01 15:03:49 +08:00
caoqianming 2bd43f0465 feat: base ticket create支持transition非必传2 2025-12-01 10:25:19 +08:00
caoqianming 4ef834570e Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-12-01 10:21:50 +08:00
caoqianming 0c1a7a4f60 feat: base ticket create支持transition非必传 2025-12-01 10:21:50 +08:00
TianyangZhang 36df498e08 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-12-01 10:08:30 +08:00
TianyangZhang 5be3f6fbe5 fix :修改提交审批报错 RelatedManager' object has no attribute 'belong_dept 2025-12-01 10:08:29 +08:00
caoqianming f3e8e8d3c2 feat: base ticket create支持transition传空 2025-12-01 09:56:52 +08:00
caoqianming df1f6f07a1 release: 3.0.2025120109 2025-12-01 09:32:44 +08:00
caoqianming 700f238a8e feat: 供应商审核通过后即可添加供应商 2025-12-01 09:16:23 +08:00
caoqianming 72f8637e19 feat: 校验SupplierAudit供应商名称已存在 2025-11-28 16:57:43 +08:00
caoqianming 8af5be4429 feat: material list filter low_inm优化 2025-11-28 15:57:21 +08:00
caoqianming 1e391f36ec feat: material list 如需获取库存数据需指定传参 2025-11-28 15:33:20 +08:00
caoqianming e494b659d6 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-26 09:55:06 +08:00
caoqianming 6112ac81f5 feat: 添加部分索引 2025-11-26 09:55:05 +08:00
TianyangZhang e98027776c feat: srm -model-Papersecret 增加 organization 申请部门字段 2025-11-25 16:38:26 +08:00
TianyangZhang ff1e60fc77 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-25 16:27:28 +08:00
TianyangZhang 9ccfcbec03 fix : srm 修改 paperrecord 字段 cor_author 字段类型 2025-11-25 16:27:26 +08:00
TianyangZhang f66e5bd9df Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-25 16:17:56 +08:00
TianyangZhang 004833292d fix:srm 修改论文审批 反存 论文台账 主要修改model 字段类型 2025-11-25 16:17:54 +08:00
caoqianming e07a1fdc4c Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-25 16:17:38 +08:00
caoqianming 8970d5b04e feat: 优化mlogbw list接口速度2 2025-11-25 16:17:37 +08:00
caoqianming 57330e6ac6 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-25 15:57:24 +08:00
caoqianming 1074fd35bd feat: 优化mlogbw list接口速度 2025-11-25 15:57:24 +08:00
TianyangZhang d0493eb87b feat: srm-patent修改字段类型 2025-11-25 15:56:21 +08:00
TianyangZhang 355be71775 fix: srm 修改专利台账 2025-11-25 14:56:05 +08:00
caoqianming 95604372a4 feat: resignation添加ticketrelate_name 2025-11-25 10:52:27 +08:00
caoqianming ae7863871a feat: base get_object加锁时注意is_deleted过滤采用base_manager 2025-11-25 10:46:44 +08:00
caoqianming 3f2db2a4af feat: base wf 调用方法支持静态方法 2025-11-25 10:23:09 +08:00
caoqianming 5db4a99611 feat: mlogbw关于wpr的校验修改以支持手动新增 2025-11-25 10:20:11 +08:00
caoqianming f70605129c feat: resignatioin提交时调用的方法 2025-11-24 15:53:56 +08:00
caoqianming 873e4bd80e feat: ResignationSerializer create bug2 2025-11-24 15:34:30 +08:00
caoqianming 2dbd7da2f8 feat: ResignationSerializer create bug 2025-11-24 15:33:57 +08:00
caoqianming e9d8402cda feat: base ticketmixin添加ticket_auto_submit_on_create 2025-11-24 14:24:45 +08:00
caoqianming 0776ff0054 fix: base wfmixin gen_ticket_data保存t_id转为str 2025-11-24 13:49:22 +08:00
caoqianming b939fcbef2 feat: resignation添加ticketMixin2 2025-11-24 13:35:32 +08:00
caoqianming dc17c01aaf feat: resignation添加ticketMixin 2025-11-24 13:22:57 +08:00
caoqianming 146756d1b8 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-24 13:21:51 +08:00
caoqianming c6dc424b33 feat: base wfmixin 修改时校验 2025-11-24 13:21:50 +08:00
TianyangZhang 54196b2a64 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-24 09:18:34 +08:00
TianyangZhang dcd41792fa fix : 修改ofm -views -vehicle && services 2025-11-24 09:18:33 +08:00
caoqianming a58d95843f feat: base ticketDetail添加create_by_name 2025-11-21 16:24:08 +08:00
caoqianming 0817589758 feat: 短信发送功能未开启 2025-11-21 15:34:56 +08:00
caoqianming 99df0166f8 feat: 批次号格式错误的校验2 2025-11-21 11:08:02 +08:00
caoqianming 5fa4881614 feat: 批次号格式错误的校验 2025-11-21 11:01:59 +08:00
caoqianming 5d7c681983 feat: mioitemcreate时接收count_send 2025-11-21 10:53:36 +08:00
caoqianming 5ed3b7c483 feat: mioitemw增加筛选条件 2025-11-20 12:27:35 +08:00
caoqianming 706cfd502b feat: wpr_bxerp优化mlogbw的获取 2025-11-20 12:00:44 +08:00
caoqianming 29f1a96c3b fix: format_json_with_placeholders 处理decimal 2025-11-19 16:53:39 +08:00
caoqianming cbd458a8a5 feat: do_out报错更明确2 2025-11-19 16:05:11 +08:00
caoqianming caf1bba050 feat: do_out报错更明确 2025-11-19 15:53:38 +08:00
caoqianming 01d93d7814 feat: 优化ana batchwork 2025-11-19 13:51:32 +08:00
caoqianming 5e81a3d3dc Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-19 10:54:43 +08:00
caoqianming f8db7adab5 feat: 改版交接支持new_wm 2025-11-19 10:54:42 +08:00
TianyangZhang a81d03e3ca feat: ofm-models borrowRecord 增加借阅数量 2025-11-19 10:26:12 +08:00
TianyangZhang 3ff6072ae1 feat: ofm-models fix bug 2025-11-18 15:52:42 +08:00
TianyangZhang ab7b08b6f2 feat : ofm -vehicle fix bug 2025-11-18 15:48:33 +08:00
TianyangZhang f3daa9fe91 feat: ofm 修改车辆model 字段 2025-11-18 15:40:13 +08:00
TianyangZhang 362497af37 feat: ofm-service fix bug 2025-11-18 14:39:15 +08:00
TianyangZhang f290138c40 feat:ofm-service 修改 bug 2025-11-18 14:32:35 +08:00
TianyangZhang e2129cc799 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-18 14:14:32 +08:00
TianyangZhang 4157dd7aa6 feat: ofm -修改 view 字段 2025-11-18 14:14:31 +08:00
caoqianming 0b2b7f2fa2 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-18 11:25:28 +08:00
caoqianming 8267f8dda6 fix: base wf工作流分类接口detail错误 2025-11-18 11:25:27 +08:00
TianyangZhang fb87fead4f feat: ofm 修改 ofm 字段 并重新生成迁移文件 2025-11-18 11:19:22 +08:00
caoqianming 62d0ce87ea Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-18 09:46:26 +08:00
caoqianming 50c21292a9 feat: base workflow添加分类字段 2025-11-18 09:46:25 +08:00
TianyangZhang 4ba952d705 feat: ofm-views 修改 车辆字段 2025-11-17 16:43:02 +08:00
TianyangZhang c6ec4f6c56 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-17 16:29:26 +08:00
TianyangZhang e093498d3c feat: ofm-vehicle 修改车辆审批申请 改完按时间段进行选择 2025-11-17 16:29:04 +08:00
caoqianming 168d917232 feat: mioitem添加count_send字段 2025-11-17 14:34:13 +08:00
caoqianming 8b74d0b121 fix: do_in 可直接取用wm 2025-11-17 13:57:10 +08:00
caoqianming e7ebdb0e8e feat: 校验改版时选择的改版物料 2025-11-17 08:51:55 +08:00
caoqianming 6d030e2c06 feat: 导入物料明细支持直接从名称等匹配3 2025-11-14 14:53:51 +08:00
caoqianming c739fcfd79 feat: 导入物料明细支持直接从名称等匹配2 2025-11-14 14:50:10 +08:00
caoqianming 0141e539e7 feat: 导入物料明细支持直接从名称等匹配 2025-11-14 14:44:39 +08:00
caoqianming 75305bd1fd feat: 增强新批次号校验 2025-11-13 16:48:08 +08:00
caoqianming 7c3e63668e feat: 提供修改编号的接口 2025-11-13 16:29:55 +08:00
caoqianming c21a39b52d feat: supplieraudit采用新工作流挂载方式 2025-11-13 14:12:04 +08:00
caoqianming 53db1cdc35 feat: base 添加ticketmixin可集成到viewset下以支持工作流 2025-11-13 13:46:04 +08:00
caoqianming 6c24017905 feat: base 可跳过短信发送 2025-11-13 13:41:03 +08:00
caoqianming 509fdb3656 fix: material get_queryset忘记return qs 2025-11-13 11:24:12 +08:00
caoqianming af09d35177 feat: 反向操作时忽略明细校验2 2025-11-13 11:01:50 +08:00
caoqianming 230b71f0aa feat: 反向操作时忽略明细校验 2025-11-13 10:59:32 +08:00
caoqianming 1d74171988 fix: material增加count__gt等查询条件 2025-11-13 10:54:46 +08:00
caoqianming 7a10da7e2b Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-13 10:31:17 +08:00
caoqianming 164f3f16bb feat: 拦截new_batch的错误 2025-11-13 10:31:16 +08:00
TianyangZhang 2799e85605 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-12 11:13:12 +08:00
TianyangZhang 097adb063e feat: fix 修改 srm 的 views 2025-11-12 11:13:11 +08:00
caoqianming 8943443e24 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-12 10:58:38 +08:00
caoqianming 1b88386193 feat: base wfservice创建出工单时处理人为提交人 2025-11-12 10:58:37 +08:00
TianyangZhang eca63e993d Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-12 10:57:05 +08:00
TianyangZhang d16801c365 feat:srm fix serializer 2025-11-12 10:56:37 +08:00
caoqianming 5109d6aa22 feat: base handle_ticket 完善transition校验 2025-11-11 15:42:59 +08:00
caoqianming 2300d5bfad feat: DatasetRecord添加filter 2025-11-11 10:53:49 +08:00
caoqianming 55292558d1 feat: handover添加selectfield 2025-11-11 10:52:54 +08:00
caoqianming aa62b04fcf feat: 交接记录返回material_changed_fname 2025-11-11 10:00:03 +08:00
caoqianming acf09d0056 feat: 改版交接必须为合批操作 2025-11-11 09:35:43 +08:00
caoqianming e001de9bb6 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-11 08:19:20 +08:00
caoqianming 3ccf7a87b0 feat: base 创建数据时检验不包含id2 2025-11-11 08:19:20 +08:00
TianyangZhang 1ecc6a9f67 feat: fix srm serializer 2025-11-10 17:00:01 +08:00
TianyangZhang afd6e60cbb feat: srm-service 去掉 platform belong_dept 2025-11-10 16:04:07 +08:00
TianyangZhang 6e5db774b7 Merge branch 'master' of http://gitea.xxhhcty.xyz:8080/zcdsj/factory 2025-11-10 15:41:38 +08:00
TianyangZhang 87ffde0945 feat: add new model platform '平台审批' 2025-11-10 15:41:37 +08:00
caoqianming 9acb5eb98a feat: base 创建数据时检验不包含id 2025-11-10 15:01:28 +08:00
caoqianming 2d81424009 fix: mioitem create时校验是否混批的bug 2025-11-10 14:43:12 +08:00
caoqianming 32b39ee423 feat: 添加供应商审核 2025-11-10 14:03:21 +08:00
caoqianming 5d85abfad1 feat: base 开始编写ticketMixin可自动挂载 2025-11-09 23:40:17 +08:00
caoqianming 5b4a8445ea feat: ResignationSerializer返回employee_id_number 2025-11-09 23:24:49 +08:00
caoqianming 3cc9b0b117 feat: base 添加EuModelViewSet 2025-11-09 23:05:35 +08:00
caoqianming 9408dbd6e5 feat: 离职申请支持删除 2025-11-09 23:05:10 +08:00
caoqianming f62bc532de feat: Resignationserializer返回employee_name 2025-11-09 22:41:11 +08:00
caoqianming 0710696aa4 feat: base handle_ticket 默认参数 2025-11-09 20:11:32 +08:00
caoqianming 058141151e feat: 优化bind_resignation 2025-11-09 14:27:54 +08:00
caoqianming 5e05582d91 fix: base handle_ticket处理ticket_title 2025-11-09 12:27:30 +08:00
caoqianming 01305eeb08 feat: base 优化wf create 2025-11-09 01:19:58 +08:00
caoqianming 030f3c62de feat: ResignationViewSet添加RetrieveModelMixin 2025-11-08 22:38:45 +08:00
caoqianming 0c97939165 fix: bind_resignation bug 2025-11-08 22:32:32 +08:00
caoqianming 4b377e115c feat: bind_resignation bug 2025-11-08 22:15:32 +08:00
caoqianming 590357468d feat: 返回TicketSimpleSerializer 2025-11-07 16:44:46 +08:00
caoqianming 66d523b711 fix: clean_data关于material的处理 2025-11-07 11:05:06 +08:00
153 changed files with 3847 additions and 1746 deletions

0
apps/asm/__init__.py Normal file
View File

3
apps/asm/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
apps/asm/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class AsmConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.asm'

View File

@ -0,0 +1,86 @@
# Generated by Django 3.2.12 on 2025-12-18 08:36
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('system', '0006_auto_20241213_1249'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('pum', '0009_supplieraudit'),
('wf', '0006_auto_20251215_1645'),
]
operations = [
migrations.CreateModel(
name='AssetLog',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('type', models.CharField(help_text='入库/出库', max_length=50, verbose_name='流水类型')),
('start_date', models.DateField(blank=True, null=True, verbose_name='启用日期')),
('items', models.JSONField(blank=True, default=list, null=True, verbose_name='资产明细')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assetlog_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('keep_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.dept', verbose_name='保管部门')),
('ticket', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='assetlog_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assetlog_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='AssetCate',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=50, unique=True, verbose_name='类别名称')),
('code', models.CharField(blank=True, max_length=50, null=True, unique=True, verbose_name='类别编码')),
('default_unit', models.CharField(default='', max_length=20, verbose_name='默认单位')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assetcate_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assetcate_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Asset',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('card_number', models.CharField(blank=True, max_length=50, null=True, unique=True, verbose_name='卡片编号')),
('name', models.CharField(max_length=100, verbose_name='固定资产名称')),
('specification', models.CharField(blank=True, max_length=100, null=True, verbose_name='规格型号')),
('quantity', models.PositiveIntegerField(default=1, verbose_name='数量')),
('start_date', models.DateField(verbose_name='启用日期')),
('canuse_year', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='可用年限')),
('original_value', models.DecimalField(decimal_places=2, max_digits=15, verbose_name='资产原值')),
('storage_location', models.CharField(blank=True, max_length=100, null=True, verbose_name='存放地点')),
('state', models.CharField(help_text='在用/闲置', max_length=50, verbose_name='使用状态')),
('unit', models.CharField(default='', max_length=50, verbose_name='计量单位')),
('note', models.TextField(blank=True, null=True, verbose_name='备注')),
('cate', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='asm.assetcate', verbose_name='资产类别')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='asset_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('keep_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='system.dept', verbose_name='保管部门')),
('keeper', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='保管人')),
('supplier', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='pum.supplier', verbose_name='供应商')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='asset_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,21 @@
# Generated by Django 3.2.12 on 2025-12-24 06:53
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('asm', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='assetlog',
name='keeper',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL, verbose_name='保管人'),
),
]

View File

39
apps/asm/models.py Normal file
View File

@ -0,0 +1,39 @@
from apps.utils.models import CommonADModel, CommonBDModel, CommonAModel
from django.db import models
class AssetCate(CommonAModel):
name = models.CharField('类别名称',max_length=50, unique=True)
code = models.CharField('类别编码',max_length=50, unique=True, null=True, blank=True)
default_unit = models.CharField('默认单位', max_length=20, default='')
class Asset(CommonAModel):
"""
TN:固定资产台账
"""
card_number = models.CharField('卡片编号',max_length=50, unique=True, blank=True, null=True)
name = models.CharField('固定资产名称',max_length=100)
specification = models.CharField("规格型号", max_length=100, blank=True, null=True)
cate = models.ForeignKey(AssetCate, verbose_name='资产类别', on_delete=models.PROTECT)
quantity = models.PositiveIntegerField("数量", default=1)
start_date = models.DateField("启用日期")
canuse_year = models.PositiveSmallIntegerField("可用年限", null=True, blank=True)
original_value = models.DecimalField('资产原值', max_digits=15, decimal_places=2)
storage_location = models.CharField("存放地点", max_length=100, blank=True, null=True)
keeper = models.ForeignKey('system.user', verbose_name='保管人', on_delete=models.SET_NULL, null=True, blank=True)
keep_dept = models.ForeignKey('system.dept', verbose_name='保管部门', on_delete=models.SET_NULL, null=True, blank=True)
state = models.CharField("使用状态", max_length=50, help_text="在用/闲置")
supplier = models.ForeignKey('pum.supplier', verbose_name='供应商', on_delete=models.SET_NULL, null=True, blank=True)
unit = models.CharField("计量单位", max_length=50, default='')
note = models.TextField("备注", blank=True, null=True)
class AssetLog(CommonADModel):
"""
TN:资产操作日志
"""
type = models.CharField("流水类型", max_length=50, help_text="入库/出库")
keep_dept = models.ForeignKey('system.dept', verbose_name='保管部门', on_delete=models.SET_NULL, null=True, blank=True)
keeper = models.ForeignKey('system.user', verbose_name='保管人', on_delete=models.SET_NULL, null=True, blank=True)
start_date = models.DateField("启用日期", null=True, blank=True)
items = models.JSONField(verbose_name='资产明细', default=list, null=True, blank=True)
ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单',
on_delete=models.PROTECT, related_name='assetlog_ticket', null=True, blank=True)

58
apps/asm/serializers.py Normal file
View File

@ -0,0 +1,58 @@
from apps.asm.models import Asset, AssetLog, AssetCate
from apps.utils.serializers import CustomModelSerializer
from apps.utils.constants import EXCLUDE_FIELDS_DEPT, EXCLUDE_FIELDS
from rest_framework import serializers
from apps.system.models import User
from apps.wf.serializers import TicketSimpleSerializer
class AssetCateSerializer(CustomModelSerializer):
class Meta:
model = AssetCate
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS
class AssetSerializer(CustomModelSerializer):
keep_dept_name = serializers.CharField(source="keep_dept.name", read_only=True)
keeper_name = serializers.CharField(source="keeper.name", read_only=True)
cate_name = serializers.CharField(source="cate.name", read_only=True)
class Meta:
model = Asset
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS
class AssetCreateSerializer(CustomModelSerializer):
class Meta:
model = Asset
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS
class AssetLogSerializer(CustomModelSerializer):
keeper_name = serializers.CharField(source="keeper.name", read_only=True)
keep_dept_name = serializers.CharField(source="keep_dept.name", read_only=True)
ticket_ = TicketSimpleSerializer(source="ticket", read_only=True)
class Meta:
model = AssetLog
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS_DEPT
class AssetLogCreateSerializer(CustomModelSerializer):
class Meta:
model = AssetLog
fields = ["keep_dept", "start_date", "items", "keeper", "type"]
extra_kwargs = {"start_date": {"required": True}, "keep_dept": {"required": True}}
def to_internal_value(self, data):
for item in data.get("items", []):
if item.get("keeper", None) is None:
item["keeper"] = data.get("keeper", None)
if item.get("keep_dept", None) is None:
item["keep_dept"] = data["keep_dept"]
if item.get("start_date", None) is None:
item["start_date"] = data["start_date"]
item["state"] = "在用"
return super().to_internal_value(data)
def validate_items(self, value):
AssetCreateSerializer(data=value, many=True).is_valid(raise_exception=True)
return value

3
apps/asm/tests.py Normal file
View File

@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

13
apps/asm/urls.py Normal file
View File

@ -0,0 +1,13 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from apps.asm.views import AssetViewSet, AssetLogViewSet, AssetCateViewSet
API_BASE_URL = 'api/asm/'
router = DefaultRouter()
router.register('assetcate', AssetCateViewSet, basename='assetcate')
router.register('asset', AssetViewSet, basename='asset')
router.register('assetlog', AssetLogViewSet, basename='assetlog')
urlpatterns = [
path(API_BASE_URL, include(router.urls)),
]

71
apps/asm/views.py Normal file
View File

@ -0,0 +1,71 @@
from apps.wf.mixins import TicketMixin
from apps.utils.viewsets import CustomModelViewSet
from apps.asm.models import Asset, AssetLog, AssetCate
from rest_framework.exceptions import ParseError
from apps.asm.serializers import AssetSerializer, AssetLogSerializer, AssetCateSerializer, AssetLogCreateSerializer
from apps.wf.models import Ticket
from apps.pum.models import Supplier
class AssetCateViewSet(CustomModelViewSet):
"""
list: 固定资产分类
固定资产分类
"""
queryset = AssetCate.objects.all()
serializer_class = AssetCateSerializer
search_fields = ['name', 'code']
class AssetViewSet(CustomModelViewSet):
"""
list: 固定资产台账
固定资产台账
"""
queryset = Asset.objects.all()
serializer_class = AssetSerializer
search_fields = ['name', 'card_number', 'specification']
filterset_fields = ['keep_dept', 'state']
class AssetLogViewSet(TicketMixin, CustomModelViewSet):
"""
list: 固定资产操作
固定资产操作
"""
queryset = AssetLog.objects.all()
serializer_class = AssetLogSerializer
create_serializer_class = AssetLogCreateSerializer
def add_info_for_list(self, data):
supplierIds = []
cateIds = []
for dataitem in data:
supplierIds.extend([item["supplier"] for item in dataitem["items"]])
cateIds.extend([item["cate"] for item in dataitem["items"]])
supplier_dict = dict(Supplier.objects.filter(id__in=supplierIds).values_list("id", "name"))
assetcate_dict = dict(AssetCate.objects.filter(id__in=cateIds).values_list("id", "name"))
for dataitem in data:
for item in dataitem["items"]:
item["supplier_name"] = supplier_dict.get(item["supplier"], None)
item["cate_name"] = assetcate_dict.get(item["cate"], None)
return data
def get_workflow_key(self, instance:AssetLog):
if instance.type == "入库":
return "wf_assetlogin"
raise ParseError("不支持的流程类型")
def gen_other_ticket_data(self, instance:AssetLog):
return {"keep_dept_name": instance.keep_dept.name}
@staticmethod
def apply(ticket: Ticket, transition, new_ticket_data: dict):
assetlog:AssetLog = ticket.assetlog_ticket
items = assetlog.items
sr = AssetSerializer(data=items, many=True)
sr.is_valid(raise_exception=True)
sr.save(create_by=ticket.create_by)

View File

@ -4,6 +4,7 @@ from jinja2 import Template
from apps.bi.models import Dataset
import concurrent
from apps.utils.sql import execute_raw_sql, format_sqldata
from apps.utils.tools import MyJSONEncoder
forbidden_keywords = ["UPDATE", "DELETE", "DROP", "TRUNCATE", "INSERT", "CREATE", "ALTER", "GRANT", "REVOKE", "EXEC", "EXECUTE"]
@ -25,7 +26,7 @@ def format_json_with_placeholders(json_str, **kwargs):
# 遍历关键字参数,将占位符替换为对应的值
for key, value in kwargs.items():
formatted_json = formatted_json.replace("{" + key + "}", json.dumps(value))
formatted_json = formatted_json.replace("{" + key + "}", json.dumps(value, cls=MyJSONEncoder))
# 格式化后的字符串依然是 JSON 字符串,没有使用 json.loads()
return formatted_json

View File

@ -146,7 +146,9 @@ class DatasetRecordViewSet(ListModelMixin, CustomGenericViewSet):
queryset = DatasetRecord.objects.all()
serializer_class = DatasetRecordSerializer
filterset_fields = {
"timex": ["year", "month", "day"]
"timex": ["year", "month", "day"],
"dataset": ["exact"],
"dataset__code": ["exact"]
}

View File

@ -670,7 +670,6 @@ class TestViewSet(CustomGenericViewSet):
Handover.objects.all().delete()
Ftest.objects.all().delete()
FtestWork.objects.all().delete()
Material.objects.get_queryset(all=True).update(count=0, count_mb=0, count_wm=0)
BatchSt.objects.all().delete()
Ticket.objects.get_queryset(all=True).delete()
return Response()

View File

@ -11,7 +11,7 @@ router.register('question', QuestionViewSet, basename='question')
router.register('paper', PaperViewSet, basename='paper')
router.register('exam', ExamViewSet, basename='exam')
router.register('examrecord', ExamRecordViewSet, basename='examrecord')
router.register('training', TrainRecordViewSet, basename='examrecord')
router.register('training', TrainRecordViewSet, basename='training')
urlpatterns = [
path(API_BASE_URL, include(router.urls)),
]

View File

@ -0,0 +1,40 @@
# Generated by Django 3.2.12 on 2025-12-15 07:16
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wf', '0005_workflow_cate'),
('em', '0022_equipment_cd_req_addr'),
]
operations = [
migrations.CreateModel(
name='Repair',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('fault_description', models.TextField(verbose_name='故障描述')),
('fault_cate', models.TextField(blank=True, null=True, verbose_name='故障类别')),
('repair_start_time', models.DateTimeField(blank=True, null=True, verbose_name='维修开始时间')),
('repair_duration', models.DecimalField(decimal_places=1, default=0.0, max_digits=5, verbose_name='维修工时(小时)')),
('repair_description', models.TextField(blank=True, null=True, verbose_name='维修描述')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='repair_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('equipment', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='repaire_equip', to='em.equipment', verbose_name='关联设备')),
('repair_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='维修人')),
('ticket', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='repair_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='repair_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,5 +1,5 @@
from django.db import models
from apps.utils.models import CommonBModel, CommonADModel
from apps.utils.models import CommonBModel, CommonADModel, CommonBDModel
from apps.system.models import User
# Create your models here.
@ -156,3 +156,20 @@ class EInspect(CommonADModel):
inspect_time = models.DateTimeField("巡检时间")
result = models.CharField(max_length=20, choices=INSPECT_RESULTS, verbose_name="巡检结果", help_text=str(INSPECT_RESULTS))
note = models.TextField("备注", null=True, blank=True)
class Repair(CommonADModel):
"""
TN:维修申请
"""
equipment = models.ForeignKey(Equipment, verbose_name="关联设备", on_delete=models.CASCADE, related_name="repaire_equip")
fault_description = models.TextField("故障描述")
fault_cate = models.TextField("故障类别", null=True, blank=True)
repair_user = models.ForeignKey("system.user", verbose_name="维修人", on_delete=models.CASCADE, null=True, blank=True)
repair_start_time = models.DateTimeField("维修开始时间", null=True, blank=True)
repair_duration = models.DecimalField("维修工时(小时)", max_digits=5, decimal_places=1, default=0.0)
repair_description = models.TextField("维修描述", null=True, blank=True)
ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单',
on_delete=models.PROTECT, related_name='repair_ticket', null=True, blank=True)

View File

@ -1,10 +1,11 @@
from apps.utils.serializers import CustomModelSerializer
from apps.em.models import Equipment, EcheckRecord, EInspect, Ecate
from apps.em.models import Equipment, EcheckRecord, EInspect, Ecate, Repair
from apps.system.models import Dept
from apps.utils.constants import EXCLUDE_FIELDS, EXCLUDE_FIELDS_BASE
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework.exceptions import ParseError
from apps.wf.serializers import TicketSimpleSerializer
class EcateSerializer(CustomModelSerializer):
@ -75,4 +76,20 @@ class EInspectSerializer(CustomModelSerializer):
class CdSerializer(serializers.Serializer):
method = serializers.CharField(label="方法名")
method = serializers.CharField(label="方法名")
class RepairCreateSerializer(CustomModelSerializer):
class Meta:
model = Repair
fields = ["id", "equipment", "fault_description", "fault_cate"]
class RepairListSerializer(CustomModelSerializer):
equipment_fullname = serializers.StringRelatedField(source="equipment", read_only=True)
repair_user_name = serializers.CharField(source="repair_user.name", read_only=True)
ticket_ = TicketSimpleSerializer(source="ticket", read_only=True)
class Meta:
model = Repair
fields = "__all__"
read_only_fields = EXCLUDE_FIELDS

View File

@ -1,6 +1,6 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from apps.em.views import EquipmentViewSet, EcheckRecordViewSet, EInspectViewSet, EcateViewSet, CdView
from apps.em.views import EquipmentViewSet, EcheckRecordViewSet, EInspectViewSet, EcateViewSet, CdView, RepairViewSet
API_BASE_URL = 'api/em/'
HTML_BASE_URL = 'dhtml/em/'
@ -10,6 +10,7 @@ router.register('equipment', EquipmentViewSet, basename='equipment')
router.register('echeckrecord', EcheckRecordViewSet, basename='echeckrecord')
router.register('einspect', EInspectViewSet, basename='einspect')
router.register('ecate', EcateViewSet, basename='ecate')
router.register('repair', RepairViewSet, basename='repair')
urlpatterns = [
path(API_BASE_URL, include(router.urls)),
path(API_BASE_URL + 'cd/', CdView.as_view()),

View File

@ -2,9 +2,10 @@ from django.shortcuts import render
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from django.utils import timezone
from apps.em.models import Equipment, EcheckRecord, EInspect, Ecate
from apps.em.models import Equipment, EcheckRecord, EInspect, Ecate, Repair
from apps.utils.viewsets import CustomModelViewSet, CustomGenericViewSet
from apps.em.serializers import EquipmentSerializer, EcheckRecordSerializer, EInspectSerializer, EcateSerializer, CdSerializer
from apps.em.serializers import (EquipmentSerializer, EcheckRecordSerializer,
EInspectSerializer, EcateSerializer, CdSerializer, RepairCreateSerializer, RepairListSerializer)
from apps.em.filters import EquipFilterSet
from rest_framework.exceptions import ParseError
from rest_framework.mixins import ListModelMixin, CreateModelMixin, DestroyModelMixin
@ -20,6 +21,9 @@ from apps.enp.services import get_last_envdata
from rest_framework.views import APIView
from apps.utils.mixins import MyLoggingMixin
import importlib
from apps.wf.mixins import TicketMixin
from apps.wf.models import Ticket
from apps.system.models import User
# Create your views here.
@ -188,4 +192,49 @@ class CdView(MyLoggingMixin, APIView):
module, func = m.rsplit(".", 1)
m = importlib.import_module(module)
f = getattr(m, func)
return Response(f(*args))
return Response(f(*args))
class RepairViewSet(TicketMixin, CustomModelViewSet):
"""
list:维修申请
维修申请
"""
queryset = Repair.objects.all()
serializer_class = RepairCreateSerializer
list_serializer_class = RepairListSerializer
retrieve_serializer_class = RepairListSerializer
select_related_fields = ["equipment", "repair_user", "create_by"]
filterset_fields = ["equipment", "repair_user", "ticket__state__type"]
search_fields = ["equipment__name", "equipment__number", "fault_description", "fault_cate", "repair_description"]
workflow_key = "wf_repair"
def gen_other_ticket_data(self, instance):
return {"equipment_fullname": str(instance.equipment)}
@staticmethod
def assgin(ticket: Ticket, transition, new_ticket_data: dict):
repair_user = new_ticket_data.get("repair_user", None)
fault_cate = new_ticket_data.get("fault_cate", None)
if repair_user and fault_cate:
repair = Repair.objects.get(ticket=ticket)
repair.repair_user = User.objects.get(id=repair_user)
repair.fault_cate = fault_cate
repair.save()
else:
raise ParseError("请分派维修人")
@staticmethod
def repair_completed(ticket: Ticket, transition, new_ticket_data: dict):
repair_start_time = new_ticket_data.get("repair_start_time", None)
repair_duration = new_ticket_data.get("repair_duration", None)
repair_description = new_ticket_data.get("repair_description", None)
if repair_start_time and repair_duration:
repair = Repair.objects.get(ticket=ticket)
repair.repair_start_time = repair_start_time
repair.repair_duration = repair_duration
repair.repair_description = repair_description
repair.save()
else:
raise ParseError("请填写维修开始时间和维修工时")

View File

@ -35,7 +35,11 @@ def translate_eval_formula(exp_str: str, year: int, month: int, day: int, hour:
for match in matches:
mpst = MpointStat.objects.filter(mpoint__code=match, type="hour", year=year, month=month, day=day, hour=hour).first()
if mpst is None:
mpoint = Mpoint.objects.get(code=match)
try:
mpoint = Mpoint.objects.get(code=match)
except Exception as e:
myLogger.error(f"找不到该测点: exp_str: {exp_str} code: {match} {e}")
raise
mpst, _ = MpointStat.objects.get_or_create(mpoint=mpoint, type="hour", year=year, month=month, day=day, hour=hour, defaults={"val": 0})
myLogger.error(f"找不到该测点的时间线数据: {match}, {year}, {month}, {day}, {hour}, 赋予0值")
if mpst:

View File

@ -0,0 +1,20 @@
# Generated by Django 3.2.12 on 2025-11-24 08:15
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('wf', '0005_workflow_cate'),
('hrm', '0020_auto_20251106_0942'),
]
operations = [
migrations.AlterField(
model_name='resignation',
name='ticket',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='resignation_ticket', to='wf.ticket', verbose_name='关联工单'),
),
]

View File

@ -0,0 +1,44 @@
# Generated by Django 3.2.12 on 2025-12-12 08:48
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('system', '0006_auto_20241213_1249'),
('wf', '0005_workflow_cate'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('hrm', '0021_alter_resignation_ticket'),
]
operations = [
migrations.CreateModel(
name='EmpNeed',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('post_need', models.TextField(verbose_name='需求岗位')),
('count_need', models.PositiveIntegerField(verbose_name='需求人数')),
('salary', models.PositiveIntegerField(verbose_name='工资报酬')),
('arrival_date', models.DateField(verbose_name='到岗日期')),
('reason', models.TextField(help_text='新增人员/该岗原人员离职或辞职或辞退需补充/其他原因', verbose_name='申请理由')),
('duty', models.TextField(verbose_name='岗位人员职责描述')),
('gender', models.CharField(help_text='男/女/不限', max_length=10, verbose_name='性别要求')),
('education', models.CharField(help_text='高中/大专/不限', max_length=50, verbose_name='学历要求')),
('professional_requirement', models.TextField(blank=True, null=True, verbose_name='相关专业及技能要求')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='empneed_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('dept_need', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system.dept', verbose_name='需求部门')),
('ticket', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='empneed_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='empneed_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,57 @@
# Generated by Django 3.2.12 on 2025-12-22 05:40
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('system', '0006_auto_20241213_1249'),
('wf', '0006_auto_20251215_1645'),
('hrm', '0022_empneed'),
]
operations = [
migrations.CreateModel(
name='EmpPersonInfo',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=20, verbose_name='姓名')),
('gender', models.CharField(default='', max_length=10, verbose_name='性别')),
('IDcard', models.CharField(max_length=20, verbose_name='身份证号')),
('phone', models.CharField(blank=True, max_length=20, null=True, validators=[django.core.validators.RegexValidator('^1[3456789]\\d{9}$', '手机号码格式不正确')], verbose_name='手机号')),
('note', models.TextField(blank=True, null=True, verbose_name='备注')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='emppersoninfo_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='emppersoninfo_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='EmpJoin',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('person', models.JSONField(default=list, verbose_name='人员信息')),
('join_date', models.DateField(verbose_name='入职日期')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='empjoin_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('dept_need', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='system.dept', verbose_name='入职部门')),
('ticket', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='empjoin_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='empjoin_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2025-12-23 03:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('hrm', '0023_empjoin_emppersoninfo'),
]
operations = [
migrations.AddField(
model_name='emppersoninfo',
name='post',
field=models.CharField(blank=True, max_length=20, null=True, verbose_name='岗位'),
),
]

View File

@ -0,0 +1,40 @@
# Generated by Django 3.2.12 on 2026-01-07 01:18
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('wf', '0006_auto_20251215_1645'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('hrm', '0024_emppersoninfo_post'),
]
operations = [
migrations.CreateModel(
name='Leave',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('start_date', models.DateTimeField(verbose_name='开始日期')),
('end_date', models.DateTimeField(verbose_name='结束日期')),
('leave_type', models.PositiveSmallIntegerField(blank=True, choices=[(10, '事假'), (20, '病假'), (30, '婚假'), (40, '丧假'), (50, '公假'), (60, '工伤'), (70, '产假'), (80, '护理假'), (90, '其他')], null=True, verbose_name='请假类型')),
('reason', models.TextField(verbose_name='请假事由')),
('hour', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='请假时长')),
('file', models.TextField(blank=True, null=True, verbose_name='证明')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='leave_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('employee', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='hrm.employee', verbose_name='员工')),
('ticket', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leave_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='leave_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -4,8 +4,9 @@ from apps.system.models import Post, User
from apps.utils.models import BaseModel, CommonADModel, CommonAModel, CommonBModel
from django.utils import timezone
from datetime import timedelta
from django.core.validators import RegexValidator
PHONE_VALIDATOR = RegexValidator(r'^1[3456789]\d{9}$', '手机号码格式不正确')
class Employee(CommonBModel):
"""
TN:员工信息
@ -202,4 +203,71 @@ class Resignation(CommonADModel):
end_date = models.DateField('离职日期')
reason = models.TextField('离职原因')
handle_date = models.DateField('办理离职交接日期', null=True, blank=True)
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单', on_delete=models.SET_NULL, null=True, blank=True)
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单', on_delete=models.SET_NULL, null=True, blank=True, related_name='resignation_ticket')
class EmpNeed(CommonADModel):
"""
TN:员工需求
"""
dept_need = models.ForeignKey('system.Dept', verbose_name='需求部门', on_delete=models.CASCADE)
post_need = models.TextField("需求岗位")
count_need = models.PositiveIntegerField('需求人数')
salary = models.PositiveIntegerField("工资报酬")
arrival_date = models.DateField('到岗日期')
reason = models.TextField('申请理由', help_text="新增人员/该岗原人员离职或辞职或辞退需补充/其他原因")
duty = models.TextField("岗位人员职责描述")
gender = models.CharField('性别要求', max_length=10, help_text="男/女/不限")
education = models.CharField('学历要求', max_length=50, help_text='高中/大专/不限')
professional_requirement = models.TextField(verbose_name="相关专业及技能要求", null=True, blank=True)
ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单',
on_delete=models.CASCADE, related_name='empneed_ticket', null=True, blank=True)
class EmpJoin(CommonADModel):
"""
TN:员工入职
"""
dept_need = models.ForeignKey('system.Dept', verbose_name='入职部门', on_delete=models.CASCADE)
ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单',
on_delete=models.CASCADE, related_name='empjoin_ticket', null=True, blank=True)
person = models.JSONField('人员信息', default=list)
join_date = models.DateField('入职日期')
class EmpPersonInfo(CommonADModel):
"""
TN:入职人员信息
"""
name = models.CharField('姓名', max_length=20)
gender = models.CharField('性别', max_length=10, default='')
IDcard = models.CharField('身份证号', max_length=20)
phone = models.CharField('手机号', max_length=20,validators=[PHONE_VALIDATOR], null=True, blank=True)
post = models.CharField('岗位', max_length=20, null=True, blank=True)
note = models.TextField('备注', null=True, blank=True)
class Leave(CommonADModel):
"""
TN:员工请假
"""
E_TYPE_CHOISE = (
(10, '事假'),
(20, '病假'),
(30, '婚假'),
(40, '丧假'),
(50, '公假'),
(60, '工伤'),
(70, '产假'),
(80, '护理假'),
(90, '其他'),
)
employee = models.ForeignKey(Employee, verbose_name='员工', on_delete=models.CASCADE)
start_date = models.DateTimeField('开始日期')
end_date = models.DateTimeField('结束日期')
leave_type = models.PositiveSmallIntegerField('请假类型', choices=E_TYPE_CHOISE, null=True, blank=True)
reason = models.TextField('请假事由')
hour = models.PositiveSmallIntegerField('请假时长', null=True, blank=True)
file = models.TextField('证明', null=True, blank=True)
ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单',
on_delete=models.CASCADE, related_name='leave_ticket', null=True, blank=True)

View File

@ -9,7 +9,7 @@ from django.utils import timezone
from apps.utils.serializers import CustomModelSerializer
from apps.utils.constants import EXCLUDE_FIELDS
from apps.hrm.models import (Certificate, ClockRecord, Employee,
NotWorkRemark, Attendance, Resignation)
NotWorkRemark, Attendance, Resignation, EmpNeed, EmpJoin, EmpPersonInfo, Leave)
from apps.system.serializers import DeptSimpleSerializer, UserSimpleSerializer
from django.db import transaction
from django.core.cache import cache
@ -17,6 +17,7 @@ from apps.utils.tools import check_id_number_strict, get_info_from_id
from rest_framework.exceptions import ParseError
from django.conf import settings
import datetime
from apps.wf.serializers import TicketSimpleSerializer
class EmployeeShortSerializer(CustomModelSerializer):
@ -319,6 +320,60 @@ class AttendanceSerializer(CustomModelSerializer):
class ResignationSerializer(CustomModelSerializer):
belong_dept_name = serializers.CharField(source="employee.belong_dept.name", read_only=True)
post_name = serializers.CharField(source="employee.post.name", read_only=True)
ticket_ = TicketSimpleSerializer(source="ticket", read_only=True)
employee_name = serializers.CharField(source="employee.name", read_only=True)
employee_id_number = serializers.CharField(source="employee.id_number", read_only=True)
class Meta:
model = Resignation
fields = '__all__'
fields = '__all__'
def update(self, instance, validated_data):
validated_data.pop('employee')
return super().update(instance, validated_data)
def create(self, validated_data):
employee:Employee = validated_data['employee']
if employee.job_state == Employee.JOB_ON:
pass
else:
raise ParseError('员工不在职,不可创建申请')
if Resignation.objects.filter(employee=employee).exists():
raise ParseError('该员工已存在离职申请')
return super().create(validated_data)
class EmpNeedSerializer(CustomModelSerializer):
dept_need_name = serializers.CharField(source='dept_need.name', read_only=True)
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
class Meta:
model = EmpNeed
fields = '__all__'
class EmpJoinSerializer(CustomModelSerializer):
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
dept_name = serializers.CharField(source='dept_need.name', read_only=True)
class Meta:
model = EmpJoin
fields = '__all__'
class EmpPersonInfoSerializer(CustomModelSerializer):
class Meta:
model = EmpPersonInfo
fields = (
'name',
'gender',
'IDcard',
'phone',
'post',
'note',
)
class LeaveSerializer(CustomModelSerializer):
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
employee_name = serializers.CharField(source='employee.name', read_only=True)
post_name = serializers.CharField(source="employee.post.name", read_only=True)
belong_dept_name = serializers.CharField(source='employee.belong_dept.name', read_only=True)
class Meta:
model = Leave
fields = '__all__'

View File

@ -498,9 +498,3 @@ class HrmService:
def bind_resignation(ticket: Ticket, transition: Transition, new_ticket_data: dict):
ins = Resignation.objects.get(id=new_ticket_data['rpj'])
if ins.ticket and ins.ticket.id != ticket.id:
raise ParseError('重复创建工单')
ticket.create_by = ins.create_by
ticket.save()

View File

@ -1,5 +1,5 @@
from apps.hrm.views import (CertificateViewSet, ClockRecordViewSet, EmployeeViewSet, NotWorkRemarkViewSet,
AttendanceViewSet, ResignationViewSet)
from apps.hrm.views import (CertificateViewSet, ClockRecordViewSet, EmployeeViewSet, NotWorkRemarkViewSet, EmpNeedViewSet,
AttendanceViewSet, ResignationViewSet, EmpJoinViewSet, LeaveViewSet)
from django.urls import path, include
from rest_framework.routers import DefaultRouter
@ -14,6 +14,9 @@ router.register('not_work_remark', NotWorkRemarkViewSet,
router.register('certificate', CertificateViewSet, basename='certificate')
router.register('attendance', AttendanceViewSet, basename='attendance')
router.register('resignation', ResignationViewSet, basename='resignation')
router.register('empneed', EmpNeedViewSet, basename='empneed')
router.register('empjoin', EmpJoinViewSet, basename='empjoin')
router.register('leave', LeaveViewSet, basename='leave')
urlpatterns = [
path(API_BASE_URL, include(router.urls)),
]

View File

@ -12,23 +12,24 @@ from rest_framework.response import Response
from apps.hrm.errors import NO_NEED_LEVEL_REMARK
from apps.hrm.filters import (CertificateFilterSet, ClockRecordFilterSet, EmployeeFilterSet,
NotWorkRemarkFilterSet)
from apps.hrm.models import Certificate, ClockRecord, Employee, NotWorkRemark, Attendance, Resignation
from apps.hrm.serializers import (CertificateCreateUpdateSerializer, CertificateSerializer, ChannelAuthoritySerializer,
from apps.hrm.models import Certificate, ClockRecord, Employee, NotWorkRemark, Attendance, Resignation, EmpNeed, EmpJoin, Leave
from apps.hrm.serializers import (CertificateCreateUpdateSerializer, CertificateSerializer, ChannelAuthoritySerializer, EmpJoinSerializer,
ClockRecordListSerializer,
EmployeeCreateUpdateSerializer, EmployeeDetailSerializer, EmployeeImproveSerializer,
EmployeeNotWorkRemarkSerializer,
EmployeeNotWorkRemarkSerializer,EmpPersonInfoSerializer,
EmployeeSerializer,
ClockRecordSimpleSerializer, ClockRecordCreateSerializer,
NotWorkRemarkListSerializer, CorrectSerializer, AttendanceSerializer,
ResignationSerializer)
ResignationSerializer, EmpNeedSerializer, LeaveSerializer)
from apps.hrm.services import HrmService
from apps.third.dahua import dhClient
from apps.third.tapis import dhapis
from apps.utils.export import export_excel
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from apps.utils.mixins import BulkCreateModelMixin, BulkDestroyModelMixin, CustomListModelMixin
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet, EuModelViewSet
from apps.utils.mixins import BulkCreateModelMixin, BulkDestroyModelMixin, CustomListModelMixin, RetrieveModelMixin
from apps.wf.models import Ticket
from apps.wf.mixins import TicketMixin
epTypeOptions = {'employee': '正式员工', 'remployee': '相关方',
'visitor': '访客', 'driver': '货车司机'}
epStateOptions = {10: '在职', 20: '离职', 30: '退休'}
@ -393,9 +394,78 @@ class CertificateViewSet(CustomModelViewSet):
ins.get_state(need_update=True)
class ResignationViewSet(CustomListModelMixin, BulkCreateModelMixin, CustomGenericViewSet):
perms_map = {"get": "*", "post": "resignation.create"}
class ResignationViewSet(TicketMixin, EuModelViewSet):
select_related_fields = ['employee', 'employee__belong_dept', 'employee__post']
queryset = Resignation.objects.all()
serializer_class = ResignationSerializer
search_fields = ["employee__name"]
search_fields = ["employee__name"]
workflow_key = "wf_resignation"
def gen_other_ticket_data(self, instance):
return {"employee_name": instance.employee.name}
@staticmethod
def update_handle_date(ticket: Ticket, transition, new_ticket_data: dict):
handle_date = new_ticket_data.get("handle_date", None)
if handle_date:
resignation = Resignation.objects.get(ticket=ticket)
resignation.handle_date = handle_date
resignation.save()
else:
raise ParseError("请填写办理离职的交接日期")
@staticmethod
def make_job_off(ticket: Ticket, transition, new_ticket_data: dict):
resignation = Resignation.objects.get(ticket=ticket)
emp = resignation.employee
emp.job_state = Employee.JOB_OFF
emp.save(update_fields=['job_state'])
user = emp.user
user.is_deleted = True
user.save(update_fields=['is_deleted'])
class EmpNeedViewSet(TicketMixin, EuModelViewSet):
queryset = EmpNeed.objects.all()
serializer_class = EmpNeedSerializer
filterset_fields = ['dept_need']
search_fields = ["dept_need__name", "post_need"]
workflow_key = "wf_empneed"
def gen_other_ticket_data(self, instance):
return {"post_need": instance.post_need}
class EmpJoinViewSet(TicketMixin, EuModelViewSet):
queryset = EmpJoin.objects.all()
serializer_class = EmpJoinSerializer
workflow_key = "wf_empjoin"
def gen_other_ticket_data(self, instance):
return {"dept_name": instance.dept_need.name if instance.dept_need else None}
@staticmethod
def approve(ticket: Ticket, transition, new_ticket_data: dict):
person = new_ticket_data.get("person", None)
EmpJoin.objects.filter(ticket=ticket).update(person=person)
if not person:
raise ParseError("请选择人员")
serializer = EmpPersonInfoSerializer(data=person, many=True)
serializer.is_valid(raise_exception=True)
serializer.save()
class LeaveViewSet(TicketMixin, EuModelViewSet):
select_related_fields = [
'employee',
'employee__belong_dept',
'ticket',
]
queryset = Leave.objects.all()
serializer_class = LeaveSerializer
filterset_fields = ['leave_type', 'employee__belong_dept']
search_fields = ["employee__name", "leave_type"]
workflow_key = "wf_leave"
def gen_other_ticket_data(self, instance):
return {"hour": instance.hour if instance.hour else None}

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2025-11-17 06:33
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('inm', '0037_alter_materialbatch_material'),
]
operations = [
migrations.AddField(
model_name='mioitem',
name='count_send',
field=models.DecimalField(blank=True, decimal_places=3, max_digits=12, null=True, verbose_name='发出数量'),
),
]

View File

@ -161,6 +161,7 @@ class MIOItem(BaseModel):
batch = models.TextField('批次号', db_index=True)
unit_price = models.DecimalField('单价', max_digits=14, decimal_places=2, null=True, blank=True)
count = models.DecimalField('出入数量', max_digits=12, decimal_places=3)
count_send = models.DecimalField('发出数量', max_digits=12, decimal_places=3, null=True, blank=True)
count_tested = models.PositiveIntegerField('已检数', null=True, blank=True)
test_date = models.DateField('检验日期', null=True, blank=True)
test_user = models.ForeignKey(

View File

@ -126,7 +126,7 @@ class MIOItemCreateSerializer(CustomModelSerializer):
class Meta:
model = MIOItem
fields = ['mio', 'warehouse', 'material',
'batch', 'count', 'assemb', 'is_testok', 'mioitemw', 'mb', 'wm', 'unit_price', 'note', "pack_index"]
'batch', 'count', 'assemb', 'is_testok', 'mioitemw', 'mb', 'wm', 'unit_price', 'note', "pack_index", "count_send"]
extra_kwargs = {
'mio': {'required': True}, 'warehouse': {'required': False},
'material': {'required': False}, 'batch': {'required': False, "allow_null": True, "allow_blank": True}}
@ -156,6 +156,8 @@ class MIOItemCreateSerializer(CustomModelSerializer):
batch = validated_data.get("batch", None)
if not batch:
batch = ""
if batch != '' and len(batch) < 5:
raise ParseError('批次号格式错误')
if material.is_hidden:
raise ParseError('隐式物料不可出入库')
if mio.type in [MIO.MIO_TYPE_RETURN_IN, MIO.MIO_TYPE_BORROW_OUT]:
@ -212,11 +214,18 @@ class MIOItemCreateSerializer(CustomModelSerializer):
mio_type = mio.type
if mio_type != "pur_in" and mio_type != "other_in":
wprIds = [i["wpr"].id for i in mioitemw]
mb_ids = list(Wpr.objects.filter(id__in=wprIds).values_list("mb__id", flat=True).distinct())
if len(mb_ids) == 1 and mb_ids[0] == instance.mb.id:
pass
else:
raise ParseError(f'{batch}物料明细中存在{len(mb_ids)}个不同物料批次')
if instance.mb:
mb_ids = list(Wpr.objects.filter(id__in=wprIds).values_list("mb__id", flat=True).distinct())
if len(mb_ids) == 1 and mb_ids[0] == instance.mb.id:
pass
else:
raise ParseError(f'{batch}物料明细中存在{len(mb_ids)}个不同物料批次')
elif instance.wm:
wm_ids = list(Wpr.objects.filter(id__in=wprIds).values_list("wm__id", flat=True).distinct())
if len(wm_ids) == 1 and wm_ids[0] == instance.wm.id:
pass
else:
raise ParseError(f'{batch}物料明细中存在{len(wm_ids)}个不同物料批次')
for item in mioitemw:
if item.get("wpr", None) is None and mio_type != "pur_in" and mio_type != "other_in":
raise ParseError(f'{item["number"]}_请提供产品明细ID')
@ -238,6 +247,15 @@ class MIOItemAListSerializer(CustomModelSerializer):
read_only_fields = EXCLUDE_FIELDS_BASE
class MIOItemListSimpleSerializer(CustomModelSerializer):
warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
material_name = serializers.StringRelatedField(
source='material', read_only=True)
class Meta:
model = MIOItem
fields = ["id", "mio", "material", "warehouse", "material_name", "warehouse_name", "batch", "count", "test_date", "count_notok"]
class MIOItemSerializer(CustomModelSerializer):
warehouse_name = serializers.CharField(source='warehouse.name', read_only=True)
material_ = MaterialSerializer(source='material', read_only=True)

View File

@ -37,11 +37,12 @@ def do_out(item: MIOItem, is_reverse: bool = False):
is_zhj = False # 是否组合件领料
if mias.exists():
is_zhj = True
mias_list = list(mias.values_list('material', 'batch', 'rate'))
for i in range(len(mias_list)):
material, batch, rate = mias_list[i]
new_count = rate * item.count # 假设 item.count 存在
action_list.append([material, batch, new_count, None])
for itema in mias:
material = itema.material
batch = itema.batch
rate = itema.rate
new_count = rate * item.count
action_list.append([material, batch, new_count, None, None])
else:
action_list = [[item.material, item.batch, item.count, defect]]
@ -88,14 +89,14 @@ def do_out(item: MIOItem, is_reverse: bool = False):
defect=defect
)
except (MaterialBatch.DoesNotExist, MaterialBatch.MultipleObjectsReturned) as e:
raise ParseError(f"批次错误!{e}")
raise ParseError(f"{str(xmaterial)}批次{xbatch}错误!{e}")
mb.count = mb.count - xcount
if mb.count < 0:
raise ParseError(f"{mb.batch}-{str(mb.material)}-批次库存不足,操作失败")
else:
mb.save()
if material.into_wm:
if xmaterial.into_wm:
# 领到车间库存(或工段)
wm, new_create = WMaterial.objects.get_or_create(
batch=xbatch, material=xmaterial,
@ -156,13 +157,14 @@ def do_in(item: MIOItem):
if mias.exists():
is_zhj = True
mias_list = mias.values_list('material', 'batch', 'rate')
for i in mias_list:
material, batch, rate = i
new_count = rate * item.count # 假设 item.count 存在
action_list.append([material, batch, new_count, None])
for itema in mias:
material = itema.material
batch = itema.batch
rate = itema.rate
new_count = rate * item.count
action_list.append([material, batch, new_count, None, None])
else:
action_list = [[item.material, item.batch, item.count, defect]]
action_list = [[item.material, item.batch, item.count, defect, item.wm]]
production_dept = None
@ -170,28 +172,31 @@ def do_in(item: MIOItem):
if is_zhj:
xbatchs = [item.batch]
for al in action_list:
xmaterial, xbatch, xcount, defect = al
xmaterial, xbatch, xcount, defect, xwm = al
if xcount <= 0:
raise ParseError("存在非正数!")
xbatchs.append(xbatch)
if material.into_wm:
wm_qs = WMaterial.objects.filter(
batch=xbatch,
material=xmaterial,
belong_dept=belong_dept,
mgroup=mgroup,
defect=defect,
state=WMaterial.WM_OK)
count_x = wm_qs.count()
if count_x == 1:
wm = wm_qs.first()
elif count_x == 0:
raise ParseError(
f'{str(xmaterial)}-{xbatch}-批次库存不存在!')
if xmaterial.into_wm:
if xwm:
wm = xwm
else:
raise ParseError(
f'{str(xmaterial)}-{xbatch}-存在多个相同批次!')
wm_qs = WMaterial.objects.filter(
batch=xbatch,
material=xmaterial,
belong_dept=belong_dept,
mgroup=mgroup,
defect=defect,
state=WMaterial.WM_OK)
count_x = wm_qs.count()
if count_x == 1:
wm = wm_qs.first()
elif count_x == 0:
raise ParseError(
f'{str(xmaterial)}-{xbatch}-批次库存不存在!')
else:
raise ParseError(
f'{str(xmaterial)}-{xbatch}-存在多个相同批次!')
# 扣减车间库存
new_count = wm.count - xcount
@ -272,7 +277,7 @@ class InmService:
"""
更新库存, 支持反向操作
"""
if not MIOItem.objects.filter(mio=instance).exists():
if is_reverse is False and not MIOItem.objects.filter(mio=instance).exists():
raise ParseError("出入库记录缺失明细,无法操作")
if instance.type == MIO.MIO_TYPE_PUR_IN: # 需要更新订单
@ -347,8 +352,6 @@ class InmService:
out = -1
"""
mioitems = MIOItem.objects.filter(mio=instance)
if not mioitems.exists():
raise ParseError("未填写物料明细")
for i in mioitems:
cls.update_mb_item(i, in_or_out, 'count')
@ -370,7 +373,10 @@ class InmService:
ddict["material_ofrom"] = i.material
if field == "count":
count_change = i.count - i.count_notok
if i.test_date is not None:
count_change = i.count - i.count_notok
else:
count_change = i.count
elif field == "count_notok":
count_change = getattr(i, field)
else:

View File

@ -122,7 +122,13 @@ def daoru_mioitem_test(path:str, mioitem:MIOItem):
FtestItem.objects.bulk_create(ftestitems)
else:
break
mioitem.test_date = test_date
mioitem.test_user = test_user
mioitem.count = MIOItemw.objects.filter(mioitem=mioitem).count()
mioitem.count_tested = MIOItemw.objects.filter(mioitem=mioitem, ftest__isnull=False).count()
mioitem.count_notok = MIOItemw.objects.filter(mioitem=mioitem, ftest__is_ok=False).count()
mioitem.save()
def daoru_mioitems(path:str, mio:MIO):
from apps.utils.snowflake import idWorker
@ -136,19 +142,30 @@ def daoru_mioitems(path:str, mio:MIO):
mioitems = []
ind = 2
while sheet[f"a{ind}"].value:
batch = sheet[f"b{ind}"].value
while sheet[f"a{ind}"].value or sheet[f"b{ind}"].value:
batch = sheet[f"e{ind}"].value
material_number = sheet[f"a{ind}"].value
try:
material = Material.objects.get(number=material_number)
except Exception as e:
raise ParseError(f"未找到物料:{material_number} {e}")
if material_number:
try:
material = Material.objects.get(number=material_number)
except Exception as e:
raise ParseError(f"未找到物料:{material_number} {e}")
else:
material_name = sheet[f"b{ind}"].value
material_model = sheet[f"d{ind}"].value
material_specification = sheet[f"c{ind}"].value
try:
material = Material.objects.get(name=material_name, model=material_model, specification=material_specification)
except Exception as e:
raise ParseError(f"未找到物料:{material_name} {material_model} {material_specification} {e}")
if batch:
pass
else:
batch = ""
count = sheet[f"c{ind}"].value
warehouse_name = sheet[f"d{ind}"].value
if batch != '' and len(batch) < 5:
raise ParseError(f'{ind}行批次号{batch}:格式错误')
count = sheet[f"f{ind}"].value
warehouse_name = sheet[f"g{ind}"].value
try:
warehouse = WareHouse.objects.get(name=warehouse_name)
except Exception as e:

View File

@ -13,10 +13,10 @@ router.register('warehouse', WarehouseVIewSet, basename='warehouse')
router.register('materialbatch', MaterialBatchViewSet,
basename='materialbatch')
router.register('mio', MIOViewSet, basename='mio')
router.register('mio/do', MioDoViewSet)
router.register('mio/sale', MioSaleViewSet)
router.register('mio/pur', MioPurViewSet)
router.register('mio/other', MioOtherViewSet)
router.register('mio/do', MioDoViewSet, basename='mio_do')
router.register('mio/sale', MioSaleViewSet, basename='mio_sale')
router.register('mio/pur', MioPurViewSet, basename='mio_pur')
router.register('mio/other', MioOtherViewSet, basename='mio_other')
router.register('mioitem', MIOItemViewSet, basename='mioitem')
router.register('mioitemw', MIOItemwViewSet, basename='mioitemw')
# router.register('pack', PackViewSet, basename='pack')

View File

@ -14,7 +14,7 @@ from apps.inm.serializers import (
MaterialBatchSerializer, WareHourseSerializer, MIOListSerializer, MIOItemSerializer, MioItemAnaSerializer,
MIODoSerializer, MIOSaleSerializer, MIOPurSerializer, MIOOtherSerializer, MIOItemCreateSerializer,
MaterialBatchDetailSerializer, MIODetailSerializer, MIOItemTestSerializer, MIOItemPurInTestSerializer,
MIOItemwSerializer, MioItemDetailSerializer, PackSerializer, PackMioSerializer)
MIOItemwSerializer, MioItemDetailSerializer, PackSerializer, PackMioSerializer, MIOItemListSimpleSerializer)
from apps.inm.serializers2 import MIOItemwCreateUpdateSerializer
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from apps.inm.services import InmService
@ -28,6 +28,7 @@ from apps.mtm.models import Material
from drf_yasg.utils import swagger_auto_schema
from drf_yasg import openapi
from django.db import connection
from datetime import datetime
# Create your views here.
@ -162,20 +163,39 @@ class MIOViewSet(CustomModelViewSet):
return mio
def add_info_for_list(self, data):
# 获取检验状态
mio_dict = {}
# 1. 收集所有mio的ID
mio_ids = [item['id'] for item in data]
# 2. 预初始化mio字典和items列表
mio_dict = {item['id']: {
**item,
'is_tested': False, # 默认值设为False
'mioitems': []
} for item in data}
# 3. 批量查询MIOItem数据
if mio_ids: # 避免空查询
mioitems = MIOItemListSimpleSerializer(
instance=MIOItem.objects.filter(
mio__id__in=mio_ids
).select_related("warehouse", "material"),
many=True
).data
# 4. 单次循环处理所有item
for item in mioitems:
mio_id = item['mio']
if mio_id in mio_dict:
mio_dict[mio_id]['mioitems'].append(item)
# 更新is_tested状态只要有一个item有test_date就为True
if item.get('test_date'):
mio_dict[mio_id]['is_tested'] = True
# 5. 直接返回原始data列表避免额外转换
for item in data:
item['is_tested'] = None
mio_dict[item['id']] = item
mioitems = list(MIOItem.objects.filter(mio__id__in=mio_dict.keys()).values_list("mio__id", "test_date"))
for item in mioitems:
mioId, test_date = item
is_tested = False
if test_date:
is_tested = True
mio_dict[mioId]['is_tested'] = is_tested
datax = [mio_dict[key] for key in mio_dict.keys()]
return datax
item.update(mio_dict[item['id']])
return data
def get_serializer_class(self):
if self.action in ['create', 'update', 'partial_update']:
@ -482,13 +502,25 @@ class MIOItemwViewSet(CustomModelViewSet):
perms_map = {'get': '*', 'post': 'mio.update', 'put': 'mio.update', 'delete': 'mio.update'}
queryset = MIOItemw.objects.all()
serializer_class = MIOItemwCreateUpdateSerializer
filterset_fields = ['mioitem', 'wpr']
filterset_fields = {
'mioitem': ['exact'],
'mioitem__material__type': ['exact'],
"wpr": ['exact'],
"number": ["exact"],
"ftest": ["isnull"],
"mioitem__mio__state": ["exact"]
}
select_related_fields = ["ftest"]
ordering = ["number", "create_time"]
ordering_fields = ["number", "create_time"]
def filter_queryset(self, queryset):
if not self.detail and not self.request.query_params.get('mioitem', None):
raise ParseError('请指定所属出入库记录明细')
if not self.detail:
if not self.request.query_params.get('mioitem', None):
if "ftest__isnull" in self.request.query_params:
pass
else:
raise ParseError('请指定所属出入库记录明细')
return super().filter_queryset(queryset)
def cal_mioitem_count(self, mioitem:MIOItem):
@ -496,6 +528,10 @@ class MIOItemwViewSet(CustomModelViewSet):
mioitem.count = count
mioitem.count_tested = MIOItemw.objects.filter(mioitem=mioitem, ftest__isnull=False).count()
mioitem.count_notok = MIOItemw.objects.filter(mioitem=mioitem, ftest__is_ok=False).count()
if mioitem.test_date is None:
mioitem.test_date = datetime.now()
if mioitem.test_user is None:
mioitem.test_user = self.request.user
mioitem.save()
def perform_create(self, serializer):

View File

@ -2,7 +2,8 @@ from django_filters import rest_framework as filters
from apps.mtm.models import Goal, Material, Route, RoutePack
from django.db.models.expressions import F
from rest_framework.exceptions import ParseError
from django.db.models import Sum, Q, Value, F, ExpressionWrapper, DecimalField
from django.db.models.functions import Coalesce
class MaterialFilter(filters.FilterSet):
tag = filters.CharFilter(method='filter_tag', label="low_inm:库存不足")
@ -27,7 +28,7 @@ class MaterialFilter(filters.FilterSet):
def filter_tag(self, queryset, name, value):
if value == 'low_inm':
queryset = queryset.exclude(count_safe=None).exclude(count_safe__lte=0).filter(
queryset = Material.annotate_count(queryset.exclude(count_safe=None).exclude(count_safe__lte=0)).filter(
count__lte=F('count_safe'))
return queryset

View File

@ -6,6 +6,8 @@ from collections import defaultdict, deque
from django.db.models import Q
from datetime import datetime, timedelta
from django.utils import timezone
from django.db.models import Sum, Q, Value, F, ExpressionWrapper, DecimalField
from django.db.models.functions import Coalesce
class Process(CommonBModel):
"""
@ -119,7 +121,30 @@ class Material(CommonAModel):
def __str__(self):
return f'{self.name}|{self.specification if self.specification else ""}|{self.model if self.model else ""}|{self.process.name if self.process else ""}'
@property
def fname(self):
return f'{self.name}|{self.specification if self.specification else ""}|{self.model if self.model else ""}|{self.process.name if self.process else ""}'
@staticmethod
def annotate_count(qs):
return qs.annotate(
count_mb=Coalesce(
Sum('mb_m__count', filter=Q(mb_m__state=10, mb_m__count__gt=0)),
Value(0),
output_field=DecimalField()
),
count_wm=Coalesce(
Sum('wm_m__count', filter=Q(wm_m__state=10, wm_m__count__gt=0)),
Value(0),
output_field=DecimalField()
)
).annotate(
count=ExpressionWrapper(
F('count_wm') + F('count_mb'),
output_field=DecimalField()
)
)
class Shift(CommonBModel):
"""TN:班次
"""
@ -204,7 +229,7 @@ class Mgroup(CommonBModel):
# 如果当前时间在结束时间之前,属于前一天
else:
return (w_s_time - timedelta(days=1)).date(), shift
# return w_s_time.date(), None
raise ParseError(f"工段{self.name}的班次规则未覆盖时间点{w_s_time.strftime('%H:%M:%S')}")
class TeamMember(BaseModel):
@ -451,7 +476,7 @@ class Route(CommonADModel):
return rqs
@classmethod
def validate_dag(cls, final_material_out:Material, rqs):
def validate_dag(cls, final_material_out:Material, rqs, check_final=True):
"""
TN:校验工艺路线是否正确
- 所有 Route 必须有 material_in material_out
@ -482,7 +507,7 @@ class Route(CommonADModel):
# 3. 检查final_material_out是否是终点
final_id = final_material_out.id
if final_id in reverse_graph:
if check_final and final_id in reverse_graph:
# raise ParseError(
# f"最终物料 {final_material_out.name}(ID:{final_id}) 不能作为任何Route的输入"
# )
@ -502,6 +527,8 @@ class Route(CommonADModel):
# 5. 检查未到达的物料
unreachable_ids = all_material_ids - visited
if check_final is False:
unreachable_ids.discard(final_id)
if unreachable_ids:
# unreachable_materials = Material.objects.filter(id__in=unreachable_ids).values_list('name', flat=True)
# raise ParseError(

View File

@ -38,13 +38,15 @@ class MaterialSerializer(CustomModelSerializer):
count = serializers.SerializerMethodField()
def get_count_mb(self, obj):
return getattr(obj, 'count_mb', 0)
return getattr(obj, 'count_mb', None)
def get_count_wm(self, obj):
return getattr(obj, 'count_wm', 0)
return getattr(obj, 'count_wm', None)
def get_count(self, obj):
return getattr(obj, 'count_mb', 0) + getattr(obj, 'count_wm', 0)
if hasattr(obj, 'count_mb') and hasattr(obj, 'count_wm'):
return getattr(obj, 'count_mb', 0) + getattr(obj, 'count_wm', 0)
return None
class Meta:
model = Material
@ -209,7 +211,10 @@ class RouteSerializer(CustomModelSerializer):
"""
material = instance.material
process = instance.process
material_out: Material = Material.objects.get_queryset(all=True).filter(type=Material.MA_TYPE_HALFGOOD, parent=material, process=process).first()
material_out: Material = Material.objects.get_queryset(all=True).filter(
type__in=[Material.MA_TYPE_HALFGOOD, Material.MA_TYPE_GOOD],
parent=material, process=process).order_by("create_time").first()
if material_out:
material_out.is_deleted = False
if material_out.parent == material:
@ -220,7 +225,10 @@ class RouteSerializer(CustomModelSerializer):
material_out.tracking = material_out_tracking
material_out.save()
return material_out
material_out = Material.objects.get_queryset(all=True).filter(name=material.name, model=material.model, process=process, specification=material.specification).first()
material_out = Material.objects.get_queryset(all=True).filter(name=material.name,
model=material.model, process=process,
specification=material.specification).order_by("create_time", "-is_hidden").first()
if material_out:
material_out.is_deleted = False
if material_out.parent is None:
@ -230,6 +238,7 @@ class RouteSerializer(CustomModelSerializer):
material_out.tracking = material_out_tracking
material_out.save()
return material_out
material_out = Material.objects.create(**{'parent': instance.material, 'process': instance.process,
'is_hidden': True, 'name': material.name,
'number': material.number,

View File

@ -44,26 +44,10 @@ class MaterialViewSet(CustomModelViewSet):
def get_queryset(self):
qs = super().get_queryset()
if self.action in ["list", "retrieve"]:
return qs.annotate(
count_wm=Coalesce(
Sum('mb_m__count', filter=Q(mb_m__state=10)),
Value(0),
output_field=DecimalField()
),
count_mb=Coalesce(
Sum('wm_m__count', filter=Q(wm_m__state=10)),
Value(0),
output_field=DecimalField()
)
).annotate(
count=ExpressionWrapper(
F('count_wm') + F('count_mb'),
output_field=DecimalField()
)
)
if self.action in ["list", "retrieve"] and self.request.query_params.get('wiith_count', None) == 'yes':
return Material.annotate_count(qs)
return qs
def perform_destroy(self, instance):
from apps.inm.models import MaterialBatch
if MaterialBatch.objects.filter(material=instance).exists():
@ -265,9 +249,12 @@ class RoutePackViewSet(CustomModelViewSet):
@transaction.atomic
def destroy(self, request, *args, **kwargs):
from apps.wpm.models import Mlog
obj: RoutePack = self.get_object()
if obj.state != RoutePack.RP_S_CREATE:
raise ParseError('该状态下不可删除')
if Mlog.objects.filter(route__routepack=obj).exists():
raise ParseError('该工艺路线包有生产记录,不可删除')
obj.delete()
Ticket.objects.filter(ticket_data__t_id=obj.id, ticket_data__t_model='routepack').delete()
return Response(status=204)
@ -395,6 +382,16 @@ class RouteViewSet(CustomModelViewSet):
raise ParseError('该工艺步骤被其他步骤引用,无法删除')
return super().perform_destroy(instance)
@action(methods=['post'], detail=False, perms_map={'post': '*'}, serializer_class=Serializer)
def dag(self, request, *args, **kwargs):
"""获取总图
获取总图
"""
materialId = self.request.data.get('product', None)
if materialId is None:
raise ParseError('缺少参数product')
return Response(Route.get_dag(rqs=Route.objects.filter(material__id=materialId)))
class SruleViewSet(CustomModelViewSet):
"""

View File

@ -1,5 +1,5 @@
from django_filters import rest_framework as filters
from apps.ofm.models import MroomBooking, BorrowRecord
from apps.ofm.models import MroomBooking, BorrowRecord, VehicleUse
from .models import LendingSeal
from apps.utils.filters import MyJsonListFilter
@ -15,6 +15,16 @@ class MroomBookingFilterset(filters.FilterSet):
"id": ["exact"]
}
class VehicleFilterset(filters.FilterSet):
class Meta:
model = VehicleUse
fields = {
'slot_vehicle__vehreg': ['exact', 'in'],
'slot_vehicle__vdate': ['exact', 'gte', 'lte'],
'create_by': ['exact'],
"id": ["exact"]
}
class SealFilter(filters.FilterSet):
seal = MyJsonListFilter(label='按印章名称查询', field_name="seal")

View File

@ -1,6 +1,7 @@
# Generated by Django 3.2.12 on 2025-06-25 09:29
# Generated by Django 3.2.12 on 2025-11-18 03:08
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
@ -11,7 +12,9 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
('system', '0006_auto_20241213_1249'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wf', '0005_workflow_cate'),
]
operations = [
@ -40,13 +43,114 @@ class Migration(migrations.Migration):
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('title', models.CharField(max_length=100, verbose_name='会议主题')),
('note', models.TextField(blank=True, null=True, verbose_name='备注')),
('participant_count', models.PositiveIntegerField(default=0, verbose_name='参会人数')),
('key_participants', models.TextField(blank=True, null=True, verbose_name='主要参会领导')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mroombooking_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mroombooking_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mrooms_ticket', to='wf.ticket', verbose_name='关联会议室')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mroombooking_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='VehicleReg',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=50, verbose_name='车辆名称')),
('brand', models.CharField(blank=True, max_length=50, null=True, verbose_name='品牌')),
('plate', models.CharField(max_length=50, verbose_name='车牌号')),
('km', models.PositiveIntegerField(blank=True, null=True, verbose_name='行驶里程')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehiclereg_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehiclereg_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='VehicleUse',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('location', models.CharField(blank=True, max_length=100, null=True, verbose_name='出发地点')),
('via', models.CharField(blank=True, max_length=100, null=True, verbose_name='途经地点')),
('destination', models.CharField(blank=True, max_length=100, null=True, verbose_name='到达地点')),
('start_km', models.PositiveIntegerField(verbose_name='出发公里数')),
('end_km', models.PositiveIntegerField(blank=True, null=True, verbose_name='归还公里数')),
('actual_km', models.PositiveIntegerField(editable=False, verbose_name='实际行驶公里数')),
('is_city', models.BooleanField(default=True, verbose_name='是否市内用车')),
('reason', models.CharField(max_length=100, verbose_name='用车事由')),
('reception', models.CharField(blank=True, max_length=50, null=True, verbose_name='接待人')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicleuse_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicleuse_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicle_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicleuse_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
('vehiclereg', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='vehicle_record', to='ofm.vehiclereg')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='VehicleSlot',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('vdate', models.DateField(db_index=True, verbose_name='使用日期')),
('slot', models.PositiveIntegerField(help_text='0-47', verbose_name='时段')),
('is_inuse', models.BooleanField(default=True, verbose_name='是否占用')),
('vehicle_use', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='slot_vehicle', to='ofm.vehicleuse')),
('vehreg', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='slot_record', to='ofm.vehiclereg')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='Publicity',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('number', models.CharField(blank=True, max_length=50, null=True, verbose_name='记录编号')),
('title', models.CharField(max_length=100, verbose_name='送审稿件标题')),
('participants', models.CharField(max_length=50, verbose_name='所有撰稿人')),
('pub_dept', models.CharField(blank=True, max_length=50, null=True, verbose_name='部室/研究院')),
('pfile', models.CharField(blank=True, max_length=100, null=True, verbose_name='稿件路径')),
('level', models.JSONField(default=list, help_text="['重要', '一般', '非涉密']", verbose_name='涉密等级')),
('content', models.JSONField(default=list, help_text="['武器装备科研生产综合事项', '其它']", verbose_name='稿件内容涉及')),
('other_content', models.CharField(blank=True, max_length=100, null=True, verbose_name='其它内容')),
('report_purpose', models.CharField(blank=True, max_length=100, null=True, verbose_name='宣传报道目的')),
('channel', models.JSONField(default=list, help_text="['互联网', '信息平台', '官微', '公开发行物', '其它']", verbose_name='发布渠道')),
('other_channel', models.CharField(blank=True, max_length=50, null=True, verbose_name='其它渠道')),
('report_name', models.CharField(blank=True, max_length=50, null=True, verbose_name='报道名称')),
('review', models.JSONField(blank=True, default=list, help_text="['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布']", null=True, verbose_name='第一撰稿人自审')),
('dept_opinion', models.CharField(blank=True, max_length=100, null=True, verbose_name='部门负责人意见')),
('disposal_method', models.CharField(blank=True, max_length=50, null=True, verbose_name='处理方式')),
('secret_level', models.CharField(blank=True, max_length=50, null=True, verbose_name='秘密等级')),
('secret_period', models.CharField(blank=True, max_length=50, null=True, verbose_name='秘密期限')),
('dept_opinion_review', models.CharField(blank=True, max_length=100, null=True, verbose_name='部门审查意见')),
('publicity_opinion', models.CharField(blank=True, max_length=100, null=True, verbose_name='宣传报道意见')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='MroomSlot',
fields=[
@ -56,11 +160,83 @@ class Migration(migrations.Migration):
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('mdate', models.DateField(db_index=True, verbose_name='会议日期')),
('slot', models.PositiveIntegerField(help_text='0-47', verbose_name='时段')),
('is_inuse', models.BooleanField(default=True, verbose_name='是否占用')),
('booking', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='slot_b', to='ofm.mroombooking')),
('mroom', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='slot_m', to='ofm.mroom')),
],
options={
'unique_together': {('mroom', 'mdate', 'slot')},
'abstract': False,
},
),
migrations.CreateModel(
name='LendingSeal',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('seal', models.JSONField(default=list, help_text='[公章,法人章,财务章,合同章,业务章,其他章]', verbose_name='印章信息')),
('seal_other', models.CharField(blank=True, max_length=50, null=True, verbose_name='其他印章')),
('filename', models.TextField(verbose_name='文件名称')),
('file', models.TextField(verbose_name='文件内容')),
('file_count', models.PositiveIntegerField(verbose_name='用印份数')),
('is_lending', models.BooleanField(default=False, verbose_name='是否借出')),
('contacts', models.CharField(blank=True, max_length=50, null=True, validators=[django.core.validators.RegexValidator('^1[3456789]\\d{9}$', '手机号码格式不正确')], verbose_name='联系方式')),
('lending_date', models.DateField(blank=True, null=True, verbose_name='借出日期')),
('return_date', models.DateField(blank=True, null=True, verbose_name='拟归还日期')),
('actual_return_date', models.DateField(blank=True, null=True, verbose_name='实际归还日期')),
('reason', models.CharField(blank=True, max_length=100, null=True, verbose_name='借用理由')),
('note', models.TextField(blank=True, null=True, verbose_name='备注')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lendingseal_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lendingseal_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='seal_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lendingseal_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='FileRecord',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=100, verbose_name='资料名称')),
('number', models.CharField(blank=True, max_length=50, null=True, verbose_name='档案编号')),
('counts', models.CharField(blank=True, max_length=10, null=True, verbose_name='文件份数')),
('location', models.CharField(blank=True, max_length=100, null=True, verbose_name='存放位置')),
('contacts', models.CharField(blank=True, max_length=50, null=True, validators=[django.core.validators.RegexValidator('^1[3456789]\\d{9}$', '手机号码格式不正确')], verbose_name='存档人电话')),
('reciver', models.CharField(blank=True, max_length=50, null=True, verbose_name='接收人(综合办)')),
('remark', models.TextField(blank=True, max_length=200, null=True, verbose_name='备注')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='filerecord_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='filerecord_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='filerecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='BorrowRecord',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('borrow_date', models.DateField(blank=True, null=True, verbose_name='借阅日期')),
('return_date', models.DateField(blank=True, null=True, verbose_name='归还日期')),
('contacts', models.CharField(blank=True, max_length=50, null=True, validators=[django.core.validators.RegexValidator('^1[3456789]\\d{9}$', '手机号码格式不正确')], verbose_name='借阅人电话')),
('remark', models.JSONField(default=list, help_text="['借阅', '复印', '查阅']", verbose_name='用途')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrowrecord_belong_dept', to='system.dept', verbose_name='所属部门')),
('borrow_file', models.ManyToManyField(related_name='borrow_records', to='ofm.FileRecord')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrowrecord_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrow_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrowrecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,48 +0,0 @@
# Generated by Django 3.2.12 on 2025-09-05 03:07
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('wf', '0002_alter_state_filter_dept'),
('system', '0006_auto_20241213_1249'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('ofm', '0001_initial'),
]
operations = [
migrations.CreateModel(
name='LendingSeal',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('seal', models.JSONField(default=list, help_text='{"seal_name": "印章名称"}', verbose_name='印章信息')),
('filename', models.TextField(verbose_name='文件名称')),
('file', models.TextField(verbose_name='文件内容')),
('file_count', models.PositiveIntegerField(verbose_name='用印份数')),
('is_lending', models.BooleanField(default=False, verbose_name='是否借出')),
('contacts', models.CharField(blank=True, max_length=50, null=True, validators=[django.core.validators.RegexValidator('^1[3456789]\\d{9}$', '手机号码格式不正确')], verbose_name='联系方式')),
('lending_date', models.DateField(blank=True, null=True, verbose_name='借出日期')),
('return_date', models.DateField(blank=True, null=True, verbose_name='拟归还日期')),
('actual_return_date', models.DateField(blank=True, null=True, verbose_name='实际归还日期')),
('reason', models.CharField(blank=True, max_length=100, null=True, verbose_name='借用理由')),
('note', models.TextField(blank=True, null=True, verbose_name='备注')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lendingseal_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lendingseal_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('submit_user', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='seal_submit_user', to=settings.AUTH_USER_MODEL, verbose_name='提交人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='seal_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='lendingseal_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,4 +1,4 @@
# Generated by Django 3.2.12 on 2025-09-08 03:11
# Generated by Django 3.2.12 on 2025-11-18 07:39
from django.db import migrations
@ -6,12 +6,12 @@ from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ofm', '0002_lendingseal'),
('ofm', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='lendingseal',
name='submit_user',
model_name='vehicleuse',
name='vehiclereg',
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2025-11-19 02:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0002_remove_vehicleuse_vehiclereg'),
]
operations = [
migrations.AddField(
model_name='borrowrecord',
name='count',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='借阅份数'),
),
]

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2.12 on 2025-12-18 08:36
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0003_borrowrecord_count'),
]
operations = [
migrations.AlterField(
model_name='vehiclereg',
name='km',
field=models.PositiveIntegerField(default=0, verbose_name='行驶里程'),
),
migrations.AlterField(
model_name='vehicleuse',
name='start_km',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='出发公里数'),
),
]

View File

@ -1,42 +0,0 @@
# Generated by Django 3.2.12 on 2025-09-10 06:26
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wf', '0002_alter_state_filter_dept'),
('ofm', '0003_remove_lendingseal_submit_user'),
]
operations = [
migrations.CreateModel(
name='Vehicle',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('start_time', models.DateField(blank=True, null=True, verbose_name='出车时间')),
('end_time', models.DateField(blank=True, null=True, verbose_name='还车时间')),
('location', models.CharField(blank=True, max_length=100, null=True, verbose_name='出发地点')),
('destination', models.CharField(blank=True, max_length=100, null=True, verbose_name='到达地点')),
('start_km', models.PositiveIntegerField(verbose_name='出发公里数')),
('end_km', models.PositiveIntegerField(verbose_name='归还公里数')),
('actual_km', models.PositiveIntegerField(editable=False, verbose_name='实际行驶公里数')),
('is_city', models.BooleanField(default=True, verbose_name='是否市内用车')),
('reason', models.CharField(max_length=100, verbose_name='用车事由')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicle_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicle_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicle_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 3.2.12 on 2026-01-04 06:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0004_auto_20251218_1636'),
]
operations = [
migrations.AlterField(
model_name='vehicleuse',
name='end_km',
field=models.PositiveIntegerField(default=0, verbose_name='归还公里数'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.12 on 2025-09-10 06:35
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0004_vehicle'),
]
operations = [
migrations.AddField(
model_name='vehicle',
name='via',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='途经地点'),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 3.2.12 on 2025-09-11 01:53
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('system', '0006_auto_20241213_1249'),
('ofm', '0005_vehicle_via'),
]
operations = [
migrations.AddField(
model_name='vehicle',
name='belong_dept',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='vehicle_belong_dept', to='system.dept', verbose_name='所属部门'),
),
]

View File

@ -1,67 +0,0 @@
# Generated by Django 3.2.12 on 2025-09-11 06:41
from django.conf import settings
import django.core.validators
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('system', '0006_auto_20241213_1249'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('ofm', '0006_vehicle_belong_dept'),
]
operations = [
migrations.AlterField(
model_name='lendingseal',
name='seal',
field=models.JSONField(default=list, help_text='[公章,法人章,财务章,合同章,业务章,其他章]', verbose_name='印章信息'),
),
migrations.CreateModel(
name='FileRecord',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=100, verbose_name='资料名称')),
('number', models.CharField(blank=True, max_length=50, null=True, verbose_name='档案编号')),
('counts', models.CharField(blank=True, max_length=10, null=True, verbose_name='文件份数')),
('location', models.CharField(blank=True, max_length=100, null=True, verbose_name='存放位置')),
('contacts', models.CharField(blank=True, max_length=50, null=True, validators=[django.core.validators.RegexValidator('^1[3456789]\\d{9}$', '手机号码格式不正确')], verbose_name='存档人电话')),
('reciver', models.CharField(blank=True, max_length=50, null=True, verbose_name='接收人(综合办)')),
('remark', models.TextField(blank=True, max_length=200, null=True, verbose_name='备注')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='filerecord_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='filerecord_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='filerecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.CreateModel(
name='BorrowRecord',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('borrow_date', models.DateField(blank=True, null=True, verbose_name='借阅日期')),
('return_date', models.DateField(blank=True, null=True, verbose_name='归还日期')),
('contacts', models.CharField(blank=True, max_length=50, null=True, validators=[django.core.validators.RegexValidator('^1[3456789]\\d{9}$', '手机号码格式不正确')], verbose_name='借阅人电话')),
('remark', models.JSONField(default=list, help_text=['借阅', '复印', '查阅'], verbose_name='用途')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrowrecord_belong_dept', to='system.dept', verbose_name='所属部门')),
('borrow_file', models.ManyToManyField(related_name='borrow_records', to='ofm.FileRecord')),
('borrow_user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='borrow_user', to=settings.AUTH_USER_MODEL)),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrowrecord_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrowrecord_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 3.2.12 on 2025-09-12 06:42
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ofm', '0007_auto_20250911_1441'),
]
operations = [
migrations.RemoveField(
model_name='borrowrecord',
name='borrow_user',
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 3.2.12 on 2025-09-12 07:00
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('wf', '0002_alter_state_filter_dept'),
('ofm', '0008_remove_borrowrecord_borrow_user'),
]
operations = [
migrations.AddField(
model_name='borrowrecord',
name='ticket',
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='borrow_ticket', to='wf.ticket', verbose_name='关联工单'),
),
]

View File

@ -1,53 +0,0 @@
# Generated by Django 3.2.12 on 2025-09-19 01:21
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('system', '0006_auto_20241213_1249'),
('ofm', '0009_borrowrecord_ticket'),
]
operations = [
migrations.AddField(
model_name='lendingseal',
name='seal_other',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='其他印章'),
),
migrations.CreateModel(
name='Publicity',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('number', models.CharField(max_length=50, verbose_name='记录编号')),
('title', models.CharField(max_length=100, verbose_name='送审稿件标题')),
('participants', models.CharField(max_length=50, verbose_name='所有撰稿人')),
('level', models.JSONField(default=list, help_text=['重要', '一般', '非涉密'], verbose_name='用途')),
('content', models.JSONField(default=list, help_text=['武器装备科研生产综合事项', '其它'], verbose_name='稿件内容涉及')),
('other_content', models.CharField(blank=True, max_length=100, null=True, verbose_name='其它内容')),
('report_purpose', models.CharField(blank=True, max_length=100, null=True, verbose_name='宣传报道目的')),
('channel', models.JSONField(default=list, help_text=['互联网', '信息平台', '官微', '公开发行物', '其它'], verbose_name='发布渠道')),
('channel_other', models.CharField(blank=True, max_length=50, null=True, verbose_name='其它渠道')),
('other_channel', models.CharField(blank=True, max_length=50, null=True, verbose_name='其它渠道')),
('report_name', models.CharField(blank=True, max_length=50, null=True, verbose_name='报道名称')),
('review', models.JSONField(default=list, help_text=['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布'], verbose_name='第一撰稿人自审')),
('dept_opinion', models.JSONField(default=list, help_text=['同意', '不同意'], verbose_name='部门负责人意见')),
('dept_opinion_review', models.CharField(blank=True, max_length=100, null=True, verbose_name='部门审查意见')),
('publicity_opinion', models.JSONField(default=list, help_text=['同意公开宣传报道', '不同意任何渠道的宣传报道'], verbose_name='宣传统战部审查意见')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,27 +0,0 @@
# Generated by Django 3.2.12 on 2025-09-24 05:59
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0010_auto_20250919_0921'),
]
operations = [
migrations.RemoveField(
model_name='publicity',
name='channel_other',
),
migrations.AddField(
model_name='publicity',
name='pfile',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='稿件路径'),
),
migrations.AddField(
model_name='publicity',
name='pub_dept',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='部室/研究院'),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 3.2.12 on 2025-09-24 06:07
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('wf', '0003_workflow_view_path'),
('ofm', '0011_auto_20250924_1359'),
]
operations = [
migrations.AddField(
model_name='publicity',
name='ticket',
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='publicity_ticket', to='wf.ticket', verbose_name='关联工单'),
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 3.2.12 on 2025-09-25 07:41
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('wf', '0003_workflow_view_path'),
('ofm', '0012_publicity_ticket'),
]
operations = [
migrations.AddField(
model_name='mroomslot',
name='ticket',
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mrooms_ticket', to='wf.ticket', verbose_name='关联会议室'),
),
]

View File

@ -1,33 +0,0 @@
# Generated by Django 3.2.12 on 2025-09-28 02:23
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('wf', '0003_workflow_view_path'),
('ofm', '0013_mroomslot_ticket'),
]
operations = [
migrations.AddField(
model_name='mroombooking',
name='ticket',
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mrooms_ticket', to='wf.ticket', verbose_name='关联会议室'),
),
migrations.AddField(
model_name='mroomslot',
name='is_inuse',
field=models.BooleanField(default=True, verbose_name='是否占用'),
),
migrations.AlterUniqueTogether(
name='mroomslot',
unique_together={('mroom', 'mdate', 'slot', 'is_inuse')},
),
migrations.RemoveField(
model_name='mroomslot',
name='ticket',
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.12 on 2025-09-28 06:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0014_auto_20250928_1023'),
]
operations = [
migrations.AlterField(
model_name='vehicle',
name='end_km',
field=models.PositiveIntegerField(blank=True, null=True, verbose_name='归还公里数'),
),
]

View File

@ -1,35 +0,0 @@
# Generated by Django 3.2.12 on 2025-09-29 07:51
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('system', '0006_auto_20241213_1249'),
('ofm', '0015_alter_vehicle_end_km'),
]
operations = [
migrations.AddField(
model_name='mroombooking',
name='belong_dept',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='mroombooking_belong_dept', to='system.dept', verbose_name='所属部门'),
),
migrations.AddField(
model_name='mroombooking',
name='key_participants',
field=models.TextField(blank=True, null=True, verbose_name='主要参会领导'),
),
migrations.AddField(
model_name='mroombooking',
name='note',
field=models.TextField(blank=True, null=True, verbose_name='备注'),
),
migrations.AddField(
model_name='mroombooking',
name='participant_count',
field=models.PositiveIntegerField(default=0, verbose_name='参会人数'),
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 3.2.12 on 2025-10-10 08:31
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0016_auto_20250929_1551'),
]
operations = [
migrations.AddField(
model_name='publicity',
name='secret_period',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='秘密期限'),
),
migrations.AlterField(
model_name='publicity',
name='level',
field=models.JSONField(default=list, help_text=['重要', '一般', '非涉密'], verbose_name='涉密等级'),
),
]

View File

@ -1,33 +0,0 @@
# Generated by Django 3.2.12 on 2025-10-11 01:22
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0017_auto_20251010_1631'),
]
operations = [
migrations.AlterField(
model_name='publicity',
name='dept_opinion',
field=models.JSONField(blank=True, default=list, help_text=['同意', '不同意'], null=True, verbose_name='部门负责人意见'),
),
migrations.AlterField(
model_name='publicity',
name='number',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='记录编号'),
),
migrations.AlterField(
model_name='publicity',
name='publicity_opinion',
field=models.JSONField(blank=True, default=list, help_text=['同意公开宣传报道', '不同意任何渠道的宣传报道'], null=True, verbose_name='宣传统战部审查意见'),
),
migrations.AlterField(
model_name='publicity',
name='review',
field=models.JSONField(blank=True, default=list, help_text=['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布'], null=True, verbose_name='第一撰稿人自审'),
),
]

View File

@ -1,28 +0,0 @@
# Generated by Django 3.2.12 on 2025-10-11 03:28
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0018_auto_20251011_0922'),
]
operations = [
migrations.AlterField(
model_name='publicity',
name='dept_opinion',
field=models.JSONField(default=list, help_text=['同意', '不同意'], verbose_name='部门负责人意见'),
),
migrations.AlterField(
model_name='publicity',
name='publicity_opinion',
field=models.JSONField(blank=True, default=list, help_text=['同意公开宣传报道', '不同意任何渠道的宣传报道'], verbose_name='宣传统战部审查意见'),
),
migrations.AlterField(
model_name='publicity',
name='review',
field=models.JSONField(blank=True, default=list, help_text=['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布'], verbose_name='第一撰稿人自审'),
),
]

View File

@ -1,28 +0,0 @@
# Generated by Django 3.2.12 on 2025-10-11 06:27
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0019_auto_20251011_1128'),
]
operations = [
migrations.AlterField(
model_name='publicity',
name='dept_opinion',
field=models.JSONField(blank=True, default=list, help_text=['同意', '不同意'], null=True, verbose_name='部门负责人意见'),
),
migrations.AlterField(
model_name='publicity',
name='publicity_opinion',
field=models.JSONField(blank=True, default=list, help_text=['同意公开宣传报道', '不同意任何渠道的宣传报道'], null=True, verbose_name='宣传统战部审查意见'),
),
migrations.AlterField(
model_name='publicity',
name='review',
field=models.JSONField(blank=True, default=list, help_text=['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布'], null=True, verbose_name='第一撰稿人自审'),
),
]

View File

@ -1,17 +0,0 @@
# Generated by Django 3.2.12 on 2025-10-17 06:50
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ofm', '0021_alter_publicity_publicity_opinion'),
]
operations = [
migrations.AlterUniqueTogether(
name='mroomslot',
unique_together=set(),
),
]

View File

@ -1,49 +0,0 @@
# Generated by Django 3.2.12 on 2025-10-21 06:08
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
('wf', '0004_workflow_view_path2'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('system', '0006_auto_20241213_1249'),
('ofm', '0022_alter_mroomslot_unique_together'),
]
operations = [
migrations.CreateModel(
name='PatentInfo',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=100, verbose_name='拟申请专利名称')),
('author', models.CharField(max_length=100, verbose_name='发明人(设计人)')),
('type', models.CharField(choices=[('invention', '发明专利'), ('utility', '实用新型专利'), ('design', '外观设计专利')], default='invention', max_length=50, verbose_name='专利类型')),
('is_public', models.BooleanField(default=False, verbose_name='是否公开')),
('area', models.CharField(choices=[('Domestic', '国内申请'), ('Foreign', '国外申请'), (' PCT', 'PCT申请')], default='Domestic', max_length=50, verbose_name='拟申请地域')),
('identified', models.BooleanField(default=False, verbose_name='是否进行过科技成果鉴定')),
('published_article', models.BooleanField(default=False, verbose_name='是否发表过文章')),
('exhibited', models.BooleanField(default=False, verbose_name='是否参与过展会展出')),
('applied_to_production', models.BooleanField(default=False, verbose_name='是否参与应用于生产/销售')),
('participated_in_exchange', models.BooleanField(default=False, verbose_name='是否参与过技术交流')),
('tech_background_pages', models.PositiveIntegerField(blank=True, null=True, verbose_name='技术背景材料页数')),
('tech_disclosure_pages', models.PositiveIntegerField(blank=True, null=True, verbose_name='技术交底材料页数')),
('novelty_report_pages', models.PositiveIntegerField(blank=True, null=True, verbose_name='查新检索报告页数')),
('diagrams_or_photos_pages', models.PositiveIntegerField(blank=True, null=True, verbose_name='图/照片页数或张数')),
('belong_dept', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='patentinfo_belong_dept', to='system.dept', verbose_name='所属部门')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='patentinfo_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='patentInfo_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='patentinfo_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,95 +0,0 @@
# Generated by Django 3.2.12 on 2025-10-22 02:05
import apps.ofm.models
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0023_patentinfo'),
]
operations = [
migrations.RemoveField(
model_name='patentinfo',
name='applied_to_production',
),
migrations.RemoveField(
model_name='patentinfo',
name='diagrams_or_photos_pages',
),
migrations.RemoveField(
model_name='patentinfo',
name='exhibited',
),
migrations.RemoveField(
model_name='patentinfo',
name='identified',
),
migrations.RemoveField(
model_name='patentinfo',
name='novelty_report_pages',
),
migrations.RemoveField(
model_name='patentinfo',
name='participated_in_exchange',
),
migrations.RemoveField(
model_name='patentinfo',
name='published_article',
),
migrations.RemoveField(
model_name='patentinfo',
name='tech_background_pages',
),
migrations.RemoveField(
model_name='patentinfo',
name='tech_disclosure_pages',
),
migrations.AddField(
model_name='patentinfo',
name='other_area',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='其它申请地域'),
),
migrations.AddField(
model_name='patentinfo',
name='tech_file',
field=models.JSONField(default=list, help_text='技术文件信息列表每个条目包含name(名称)page(页数)字段', verbose_name='技术文件'),
),
migrations.AddField(
model_name='patentinfo',
name='tech_status',
field=models.JSONField(blank=True, default=list, help_text='技术状态信息列表每个条目包含name(名称)、status(状态)、file(文件)字段', verbose_name='技术状态'),
),
migrations.AlterField(
model_name='borrowrecord',
name='remark',
field=models.JSONField(default=list, help_text="['借阅', '复印', '查阅']", verbose_name='用途'),
),
migrations.AlterField(
model_name='publicity',
name='channel',
field=models.JSONField(default=list, help_text="['互联网', '信息平台', '官微', '公开发行物', '其它']", verbose_name='发布渠道'),
),
migrations.AlterField(
model_name='publicity',
name='content',
field=models.JSONField(default=list, help_text="['武器装备科研生产综合事项', '其它']", verbose_name='稿件内容涉及'),
),
migrations.AlterField(
model_name='publicity',
name='dept_opinion',
field=models.JSONField(blank=True, default=list, help_text="['同意', '不同意']", null=True, verbose_name='部门负责人意见'),
),
migrations.AlterField(
model_name='publicity',
name='level',
field=models.JSONField(default=list, help_text="['重要', '一般', '非涉密']", verbose_name='涉密等级'),
),
migrations.AlterField(
model_name='publicity',
name='review',
field=models.JSONField(blank=True, default=list, help_text="['内容不涉及国家秘密和商业秘密,申请公开', '内容涉及国家秘密,申请按涉密渠道发布']", null=True, verbose_name='第一撰稿人自审'),
),
]

View File

@ -1,18 +0,0 @@
# Generated by Django 3.2.12 on 2025-10-24 05:46
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0024_auto_20251022_1005'),
]
operations = [
migrations.AlterField(
model_name='patentinfo',
name='area',
field=models.CharField(choices=[('Domestic', '国内申请'), ('Foreign', '国外申请'), ('PCT', 'PCT申请')], default='Domestic', max_length=50, verbose_name='拟申请地域'),
),
]

View File

@ -1,41 +0,0 @@
# Generated by Django 3.2.12 on 2025-10-29 03:13
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wf', '0004_workflow_view_path2'),
('ofm', '0025_alter_patentinfo_area'),
]
operations = [
migrations.CreateModel(
name='PaperSe',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('paper_name', models.CharField(max_length=100, verbose_name='拟发表论文名称')),
('publication_name', models.CharField(max_length=100, verbose_name='拟投期刊名称')),
('author', models.CharField(max_length=100, verbose_name='作者')),
('paper_type', models.CharField(max_length=100, verbose_name='拟发表文章类型')),
('is_chinese_core', models.BooleanField(default=False, verbose_name='是否为中文核心')),
('is_sci', models.BooleanField(default=False, verbose_name='是否被SCI/EI收录')),
('tech_status', models.JSONField(blank=True, default=list, help_text='技术状态信息列表每个条目包含name(名称)、status(状态)、file(文件)字段', verbose_name='技术状态')),
('tech_file', models.JSONField(default=list, help_text='技术文件信息列表每个条目包含name(名称)page(页数)字段', verbose_name='技术文件')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='paperse_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='paperse_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='paperse_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,44 +0,0 @@
# Generated by Django 3.2.12 on 2025-10-29 06:26
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wf', '0004_workflow_view_path2'),
('ofm', '0026_paperse'),
]
operations = [
migrations.CreateModel(
name='Papersecret',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('paper_name', models.CharField(max_length=100, verbose_name='拟发表论文名称')),
('publication_name', models.CharField(max_length=100, verbose_name='拟投期刊名称')),
('author', models.CharField(max_length=100, verbose_name='作者')),
('paper_type', models.CharField(max_length=100, verbose_name='拟发表文章类型')),
('is_chinese_core', models.BooleanField(default=False, verbose_name='是否为中文核心')),
('is_sci', models.BooleanField(default=False, verbose_name='是否被SCI/EI收录')),
('tech_status', models.JSONField(blank=True, default=list, help_text='技术状态信息列表每个条目包含name(名称)、status(状态)、file(文件)字段', verbose_name='技术状态')),
('tech_file', models.JSONField(default=list, help_text='技术文件信息列表每个条目包含name(名称)page(页数)字段', verbose_name='技术文件')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='papersecret_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='paperse_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='papersecret_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
migrations.DeleteModel(
name='PaperSe',
),
]

View File

@ -1,20 +0,0 @@
# Generated by Django 3.2.12 on 2025-10-30 05:55
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('system', '0006_auto_20241213_1249'),
('ofm', '0027_auto_20251029_1426'),
]
operations = [
migrations.AddField(
model_name='papersecret',
name='belong_dept',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='papersecret_belong_dept', to='system.dept', verbose_name='所属部门'),
),
]

View File

@ -1,35 +0,0 @@
# Generated by Django 3.2.12 on 2025-11-03 01:39
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('ofm', '0028_papersecret_belong_dept'),
]
operations = [
migrations.RemoveField(
model_name='patentinfo',
name='belong_dept',
),
migrations.RemoveField(
model_name='patentinfo',
name='create_by',
),
migrations.RemoveField(
model_name='patentinfo',
name='ticket',
),
migrations.RemoveField(
model_name='patentinfo',
name='update_by',
),
migrations.DeleteModel(
name='Papersecret',
),
migrations.DeleteModel(
name='PatentInfo',
),
]

View File

@ -1,23 +0,0 @@
# Generated by Django 3.2.12 on 2025-11-05 09:15
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0029_auto_20251103_0939'),
]
operations = [
migrations.AlterField(
model_name='vehicle',
name='end_time',
field=models.DateTimeField(blank=True, null=True, verbose_name='还车时间'),
),
migrations.AlterField(
model_name='vehicle',
name='start_time',
field=models.DateTimeField(blank=True, null=True, verbose_name='出车时间'),
),
]

View File

@ -1,28 +0,0 @@
# Generated by Django 3.2.12 on 2025-11-06 08:08
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ofm', '0030_auto_20251105_1715'),
]
operations = [
migrations.AddField(
model_name='publicity',
name='disposal_method',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='处理方式'),
),
migrations.AddField(
model_name='publicity',
name='secret_level',
field=models.CharField(blank=True, max_length=50, null=True, verbose_name='秘密等级'),
),
migrations.AlterField(
model_name='publicity',
name='dept_opinion',
field=models.CharField(blank=True, max_length=100, null=True, verbose_name='部门负责人意见'),
),
]

View File

@ -26,6 +26,7 @@ class Mroom(CommonADModel):
location = models.CharField('位置', max_length=100)
capacity = models.PositiveIntegerField('容纳人数')
class MroomBooking(CommonBDModel):
"""TN: 会议室预定信息"""
# belong_dept 是预定部门
@ -46,10 +47,45 @@ class MroomSlot(BaseModel):
is_inuse = models.BooleanField('是否占用', default=True)
# class Seal(BaseModel):
# """TN: 印章类型"""
# name = models.CharField('印章名称', max_length=50, unique=True)
class VehicleReg(CommonADModel):
"""TN: 车辆台账"""
name = models.CharField('车辆名称', max_length=50)
brand = models.CharField('品牌', max_length=50, null=True, blank=True)
plate = models.CharField('车牌号', max_length=50)
km = models.PositiveIntegerField('行驶里程', default=0)
class VehicleUse(CommonBDModel):
"""TN: 用车记录"""
location = models.CharField('出发地点', null=True, blank=True, max_length=100)
via = models.CharField('途经地点', null=True, blank=True, max_length=100)
destination = models.CharField('到达地点', null=True, blank=True, max_length=100)
start_km = models.PositiveIntegerField('出发公里数', null=True, blank=True)
end_km = models.PositiveIntegerField('归还公里数', default=0)
actual_km = models.PositiveIntegerField('实际行驶公里数', editable=False)
is_city = models.BooleanField('是否市内用车', default=True)
reason = models.CharField('用车事由', max_length=100)
reception = models.CharField('接待人', max_length=50, blank=True, null=True)
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单',
on_delete=models.SET_NULL, related_name='vehicle_ticket', null=True, blank=True, db_constraint=False)
def save(self, *args, **kwargs):
if self.start_km is not None:
self.start_km = int(self.start_km)
if self.end_km is not None:
self.end_km = int(self.end_km)
if self.start_km <= self.end_km:
self.actual_km = self.end_km - self.start_km
else:
raise ParseError(f'归还公里数不能小于出发公里数, 出发公里数组为 {self.start_km} km')
else:
self.actual_km = 0
return super().save(*args, **kwargs)
class VehicleSlot(BaseModel):
vehreg = models.ForeignKey(VehicleReg, on_delete=models.CASCADE, related_name="slot_record")
vehicle_use = models.ForeignKey(VehicleUse, on_delete=models.CASCADE, related_name="slot_vehicle")
vdate = models.DateField('使用日期', db_index=True)
slot = models.PositiveIntegerField('时段', help_text='0-47')
is_inuse = models.BooleanField('是否占用', default=True)
class LendingSeal(CommonBDModel):
"""TN: 印章外出用印信息"""
@ -69,32 +105,6 @@ class LendingSeal(CommonBDModel):
on_delete=models.SET_NULL, related_name='seal_ticket', null=True, blank=True, db_constraint=False)
note = models.TextField('备注', null=True, blank=True)
class Vehicle(CommonBDModel):
"""TN: 用车申请"""
start_time = models.DateTimeField('出车时间', blank=True, null=True)
end_time = models.DateTimeField('还车时间', blank=True, null=True)
location = models.CharField('出发地点', null=True, blank=True, max_length=100)
via = models.CharField('途经地点', null=True, blank=True, max_length=100)
destination = models.CharField('到达地点', null=True, blank=True, max_length=100)
start_km = models.PositiveIntegerField('出发公里数')
end_km = models.PositiveIntegerField('归还公里数', null=True, blank=True)
actual_km = models.PositiveIntegerField('实际行驶公里数', editable=False)
is_city = models.BooleanField('是否市内用车', default=True)
reason = models.CharField('用车事由', max_length=100)
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单',
on_delete=models.SET_NULL, related_name='vehicle_ticket', null=True, blank=True, db_constraint=False)
def save(self, *args, **kwargs):
if self.end_km:
if self.start_km <= self.end_km:
self.actual_km = self.end_km - self.start_km
else:
raise ParseError('归还公里数不能小于出发公里数')
else:
self.actual_km = 0
return super().save(*args, **kwargs)
class FileRecord(CommonBDModel):
"""TN: 档案台账"""
name = models.CharField('资料名称', max_length=100)
@ -105,12 +115,12 @@ class FileRecord(CommonBDModel):
reciver = models.CharField('接收人(综合办)', max_length=50, null=True, blank=True)
remark = models.TextField('备注', max_length=200, null=True, blank=True)
class BorrowRecord(CommonBDModel):
"""TN: 借阅、复印、查阅记录"""
borrow_file = models.ManyToManyField(FileRecord, related_name="borrow_records")
borrow_date = models.DateField('借阅日期', null=True, blank=True)
return_date = models.DateField('归还日期', null=True, blank=True)
count = models.PositiveIntegerField('借阅份数', null=True, blank=True)
contacts = models.CharField('借阅人电话', max_length=50, validators=[phone_validator], null=True, blank=True)
remark = models.JSONField('用途', default=list, help_text=str(['借阅', '复印', '查阅']))
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单',

View File

@ -1,4 +1,4 @@
from .models import (Mroom, MroomBooking, MroomSlot, LendingSeal, Vehicle, FileRecord, BorrowRecord, Publicity)
from .models import (Mroom, MroomBooking, MroomSlot, LendingSeal, VehicleUse, FileRecord, BorrowRecord, Publicity, VehicleReg, VehicleSlot)
from apps.utils.serializers import CustomModelSerializer
from rest_framework import serializers
from django.db import transaction
@ -33,14 +33,17 @@ class MroomBookingSerializer(CustomModelSerializer):
mdate = validated_data.pop('mdate')
booking = super().create(validated_data)
MroomSlot.objects.filter(booking=booking).delete()
for slot in slots:
if slot < 0 or slot > 47:
raise ParseError("时段索引超出范围")
ms_exists = MroomSlot.objects.filter(mroom=mroom, mdate=mdate, slot=slot, is_inuse=True).exists()
if ms_exists:
raise ParseError("时段已预订,请刷新重选")
MroomSlot.objects.create(booking=booking, slot=slot, mdate=mdate, mroom=mroom, is_inuse=True)
return booking
if slots:
for slot in slots:
if slot < 0 or slot > 47:
raise ParseError("时段索引超出范围")
ms_exists = MroomSlot.objects.filter(mroom=mroom, mdate=mdate, slot=slot, is_inuse=True).exists()
if ms_exists:
raise ParseError("时段已预订,请刷新重选")
MroomSlot.objects.create(booking=booking, slot=slot, mdate=mdate, mroom=mroom, is_inuse=True)
return booking
else:
raise ParseError("请选择时段")
def update(self, instance, validated_data):
mroom = validated_data.pop('mroom')
@ -48,16 +51,17 @@ class MroomBookingSerializer(CustomModelSerializer):
mdate = validated_data.pop('mdate')
booking = super().update(instance, validated_data)
MroomSlot.objects.filter(booking=instance).delete()
for slot in slots:
if slot < 0 or slot > 47:
raise ParseError("时段索引超出范围")
ms_exists = MroomSlot.objects.filter(mroom=mroom, mdate=mdate, slot=slot, is_inuse=True).exists()
if ms_exists:
raise ParseError("时段已预订,请刷新重选")
MroomSlot.objects.create(booking=booking, slot=slot, mdate=mdate, mroom=mroom, is_inuse=True)
return booking
if slots:
for slot in slots:
if slot < 0 or slot > 47:
raise ParseError("时段索引超出范围")
ms_exists = MroomSlot.objects.filter(mroom=mroom, mdate=mdate, slot=slot, is_inuse=True).exists()
if ms_exists:
raise ParseError("时段已预订,请刷新重选")
MroomSlot.objects.create(booking=booking, slot=slot, mdate=mdate, mroom=mroom, is_inuse=True)
return booking
else:
raise ParseError("请选择时段")
class MroomSlotSerializer(CustomModelSerializer):
booking_title = serializers.CharField(source='booking.title', read_only=True)
class Meta:
@ -65,6 +69,72 @@ class MroomSlotSerializer(CustomModelSerializer):
fields = '__all__'
class VehicleRecordSerializer(CustomModelSerializer):
class Meta:
model = VehicleReg
fields = '__all__'
class VehicleUseSerializer(CustomModelSerializer):
vehreg = serializers.PrimaryKeyRelatedField(queryset=VehicleReg.objects.all(), write_only=True, label="车辆信息")
vdate = serializers.DateField(write_only=True, label="预订日期")
slots = serializers.ListField(child=serializers.IntegerField(), write_only=True, label="时段索引")
create_by_name = serializers.CharField(source='create_by.username', read_only=True)
create_by_phone = serializers.CharField(source='create_by.phone', read_only=True)
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
class Meta:
model = VehicleUse
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS + ['actual_km']
extra_kwargs = {'belong_dept': {'required': True}}
def create(self, validated_data):
vehreg = validated_data.pop('vehreg')
slots = validated_data.pop('slots')
vdate = validated_data.pop('vdate')
validated_data['start_km'] = vehreg.km
if validated_data.get('end_km', None):
vehreg.km= validated_data['end_km']
vehicle_use = super().create(validated_data)
VehicleSlot.objects.filter(vehicle_use=vehicle_use).delete()
if slots:
for slot in slots:
if slot < 0 or slot > 47:
raise ParseError("时段索引超出范围")
ms_exists = VehicleSlot.objects.filter(vehreg=vehreg, vdate=vdate, slot=slot, is_inuse=True).exists()
if ms_exists:
raise ParseError("时段已预订,请刷新重选")
VehicleSlot.objects.create(vehicle_use=vehicle_use, slot=slot, vdate=vdate, vehreg=vehreg, is_inuse=True)
return vehicle_use
else:
raise ParseError("请选择时段")
def update(self, instance, validated_data):
vehreg = validated_data.pop('vehreg')
slots = validated_data.pop('slots')
vdate = validated_data.pop('vdate')
vehicle_use = super().update(instance, validated_data)
VehicleSlot.objects.filter(vehicle_use=vehicle_use).delete()
if slots:
for slot in slots:
if slot < 0 or slot > 47:
raise ParseError("时段索引超出范围")
ms_exists = VehicleSlot.objects.filter(vehreg=vehreg, vdate=vdate, slot=slot, is_inuse=True).exists()
if ms_exists:
raise ParseError("时段已预订,请刷新重选")
VehicleSlot.objects.create(vehicle_use=vehicle_use, slot=slot, vdate=vdate, vehreg=vehreg, is_inuse=True)
return vehicle_use
else:
raise ParseError("请选择时段")
class VehSlotSerializer(CustomModelSerializer):
veh_name = serializers.CharField(source='vehreg.name', read_only=True)
class Meta:
model = VehicleSlot
fields = '__all__'
class LendingSealSerializer(CustomModelSerializer):
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
@ -74,17 +144,6 @@ class LendingSealSerializer(CustomModelSerializer):
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS
class VehicleSerializer(CustomModelSerializer):
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
class Meta:
model = Vehicle
fields = '__all__'
read_only_fields = EXCLUDE_FIELDS + ['actual_km']
class FileRecordSerializer(CustomModelSerializer):
create_by_name = serializers.CharField(source='create_by.name', read_only=True)
belong_dept_name = serializers.CharField(source='belong_dept.name', read_only=True)

View File

@ -1,7 +1,7 @@
from apps.wf.models import Ticket
# TicketFlow, Transition, Workflow, CustomField, State,
from apps.ofm.models import LendingSeal, Vehicle, BorrowRecord, Publicity, MroomBooking, MroomSlot
from apps.ofm.models import LendingSeal, VehicleUse, BorrowRecord, Publicity, MroomBooking, MroomSlot
from rest_framework.exceptions import ParseError
@ -29,10 +29,11 @@ def mroombooking_reject(ticket: Ticket, transition, new_ticket_data: dict):
ins = MroomBooking.objects.get(id=new_ticket_data['t_id'])
MroomSlot.objects.filter(booking=ins).update(is_inuse=False)
def bind_lendingseal(ticket: Ticket, transition, new_ticket_data: dict):
ticket_data = ticket.ticket_data
ins = LendingSeal.objects.get(id=new_ticket_data['t_id'])
ins.actual_return_date = None
ticket_data = ticket.ticket_data
ticket_data.update({
't_model': 'LendingSeal',
't_id': ins.id,
@ -59,7 +60,7 @@ def lending_save_ticket_data(ticket: Ticket, new_ticket_data: dict, **kwargs):
def bind_vehicle(ticket: Ticket, transition, new_ticket_data: dict):
ins = Vehicle.objects.get(id=new_ticket_data['t_id'])
ins = VehicleUse.objects.get(id=new_ticket_data['t_id'])
ticket_data = ticket.ticket_data
ticket_data.update({
't_model': 'Vehicle',
@ -67,7 +68,6 @@ def bind_vehicle(ticket: Ticket, transition, new_ticket_data: dict):
'is_city': ins.is_city
})
ins.actual_km = None
ins.end_time = None
ticket.ticket_data = ticket_data
ticket.create_by = ins.create_by
ticket.save()
@ -77,8 +77,8 @@ def bind_vehicle(ticket: Ticket, transition, new_ticket_data: dict):
def vehicle_save_ticket_data(ticket: Ticket, new_ticket_data: dict, **kwargs):
try:
obj = Vehicle.objects.get(id=new_ticket_data['t_id'])
except Vehicle.DoesNotExist:
obj = VehicleUse.objects.get(id=new_ticket_data['t_id'])
except VehicleUse.DoesNotExist:
raise ParseError("Publicity t_id 不存在")
data_save = {k: v for k, v in new_ticket_data.items() if k not in ['t_model', 't_id']}

View File

@ -1,6 +1,7 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from apps.ofm.views import (MroomViewSet, MroomBookingViewSet, MroomSlotViewSet,LendingSealViewSet, VehicleViewSet, FilerecordViewSet, FileborrowViewSet, PublicityViewSet)
from apps.ofm.views import (MroomViewSet, MroomBookingViewSet, MroomSlotViewSet,LendingSealViewSet, VehicleUseViewSet,
VehicleRegViewSet, VehicleSlotViewSet, FilerecordViewSet, FileborrowViewSet, PublicityViewSet)
API_BASE_URL = 'api/ofm/'
HTML_BASE_URL = 'dhtml/ofm/'
@ -11,7 +12,9 @@ router.register('mroombooking', MroomBookingViewSet, basename='mroombooking')
router.register('mroomslot', MroomSlotViewSet, basename='mroomslot')
# router.register('sealmanage', SealManageViewSet, basename='sealmanage')
router.register('lendingseal', LendingSealViewSet, basename='lendingseal')
router.register('vehicle', VehicleViewSet, basename='vehicle')
router.register('vehicle-use', VehicleUseViewSet, basename='vehicle_use')
router.register('vsolt', VehicleSlotViewSet, basename='vslot')
router.register('vehicle', VehicleRegViewSet, basename='vehicle')
router.register('filerecord', FilerecordViewSet, basename='filerecord')
router.register('fileborrow', FileborrowViewSet, basename='fileborrow')
router.register('publicity', PublicityViewSet, basename='publicity')

View File

@ -1,12 +1,12 @@
from django.shortcuts import render
from apps.utils.viewsets import CustomModelViewSet, CustomGenericViewSet
from .models import Mroom, MroomBooking, MroomSlot, LendingSeal, Vehicle, FileRecord, BorrowRecord, Publicity
from .models import Mroom, MroomBooking, MroomSlot, LendingSeal, VehicleUse, VehicleSlot, VehicleReg, FileRecord, BorrowRecord, Publicity
from .serializers import (MroomSerializer, MroomBookingSerializer, MroomSlotSerializer, LendingSealSerializer,
VehicleSerializer, FileRecordSerializer, BorrowRecordSerializer, PublicitySerializer)
VehicleUseSerializer, VehicleRecordSerializer, VehSlotSerializer, FileRecordSerializer, BorrowRecordSerializer, PublicitySerializer)
from apps.utils.mixins import CustomListModelMixin
from rest_framework.exceptions import ParseError
from apps.ofm.filters import MroomBookingFilterset, SealFilter, BorrowRecordFilter
from apps.ofm.filters import MroomBookingFilterset, SealFilter, BorrowRecordFilter, VehicleFilterset
from rest_framework.response import Response
class MroomViewSet(CustomModelViewSet):
@ -115,8 +115,6 @@ class LendingSealViewSet(CustomModelViewSet):
印章外出
"""
perms_map = {'get': '*', 'post': 'seal.update',
'put': 'seal.update', 'delete': 'seal.delete'}
queryset = LendingSeal.objects.all()
serializer_class = LendingSealSerializer
filterset_class = SealFilter
@ -124,15 +122,106 @@ class LendingSealViewSet(CustomModelViewSet):
data_filter = True
class VehicleViewSet(CustomModelViewSet):
class VehicleRegViewSet(CustomModelViewSet):
"""list: 车辆
车辆
"""
queryset = Vehicle.objects.all()
serializer_class = VehicleSerializer
queryset = VehicleReg.objects.all()
serializer_class = VehicleRecordSerializer
ordering = ["-create_time"]
class VehicleUseViewSet(CustomModelViewSet):
"""list: 车辆使用
车辆使用
"""
queryset = VehicleUse.objects.all()
serializer_class = VehicleUseSerializer
select_related_fields = ["create_by", "ticket", "belong_dept"]
filterset_class = VehicleFilterset
def add_info_for_list(self, data):
vehicle_ids = [d["id"] for d in data]
slots = VehicleSlot.objects.filter(vehicle_use__in=vehicle_ids).order_by("vehreg", "vehicle_use", "vdate", "slot")
vehicle_info = {}
for slot in slots:
vehicle_id = slot.vehicle_use.id
if vehicle_id not in vehicle_info:
vehicle_info[vehicle_id] = {
"vdate": slot.vdate.strftime("%Y-%m-%d"), # 格式化日期
"vehreg": slot.vehreg.id,
"vehreg_name": slot.vehreg.name, # 会议室名称
"time_ranges": [], # 存储时间段(如 ["8:00-9:00", "10:00-11:30"]
"current_slots": [], # 临时存储连续的slot用于合并
}
# 检查是否连续当前slot是否紧接上一个slot
current_slots = vehicle_info[vehicle_id]["current_slots"]
if not current_slots or slot.slot == current_slots[-1] + 1:
current_slots.append(slot.slot)
else:
# 如果不连续先把当前连续的slot转换成时间段
if current_slots:
start_time = self._slot_to_time(current_slots[0])
end_time = self._slot_to_time(current_slots[-1] + 1)
vehicle_info[vehicle_id]["time_ranges"].append(f"{start_time}-{end_time}")
current_slots.clear()
current_slots.append(slot.slot)
# 处理最后剩余的连续slot
for info in vehicle_info.values():
if info["current_slots"]:
start_time = self._slot_to_time(info["current_slots"][0])
end_time = self._slot_to_time(info["current_slots"][-1] + 1)
info["time_ranges"].append(f"{start_time}-{end_time}")
info["slots"] = info.pop("current_slots") # 清理临时数据
for item in data:
item.update(vehicle_info.get(item["id"], {}))
return data
@staticmethod
def _slot_to_time(slot):
"""将slot (0-47) 转换为 HH:MM 格式的时间字符串"""
hours = slot // 2
minutes = (slot % 2) * 30
return f"{hours:02d}:{minutes:02d}"
def perform_update(self, serializer):
ins:VehicleUse = self.get_object()
ticket = ins.ticket
if ticket is None or ticket.state.type == 1:
pass
else:
raise ParseError("存在审批单,不允许修改")
if ins.create_by and ins.create_by != self.request.user:
raise ParseError("只允许创建者修改")
return super().perform_update(serializer)
def perform_destroy(self, instance):
if instance.create_by and instance.create_by != self.request.user:
raise ParseError("只允许创建者删除")
ticket = instance.ticket
if ticket is None or ticket.state.type == 1:
pass
else:
raise ParseError("存在审批单,不允许删除")
if ticket:
ticket.delete()
instance.delete()
class VehicleSlotViewSet(CustomListModelMixin, CustomGenericViewSet):
"""list:
会议室预订时段
"""
queryset = VehicleSlot.objects.all()
serializer_class = VehSlotSerializer
filterset_fields = ["vehreg", "vehicle_use", "vdate", "is_inuse"]
class FilerecordViewSet(CustomModelViewSet):
"""list: 文件
@ -167,120 +256,3 @@ class PublicityViewSet(CustomModelViewSet):
ordering = ["-create_time", "number"]
# class PatentInfoViewSet(CustomModelViewSet):
# """list: 专利
# 专利
# """
# queryset = PatentInfo.objects.all()
# serializer_class = PatentInfoSerializer
# filterset_fields = ["name", "author", "type"]
# ordering = ["-create_time", "name", "author", "type"]
# class PapersecretViewSet(CustomModelViewSet):
# """list: 论文申密审批
# 论文申密审批
# """
# queryset = Papersecret.objects.all()
# serializer_class = PaperSeSerializer
# filterset_fields = ["paper_name", "author"]
# ordering = ["-create_time", "paper_name"]
# class PatentRecordViewSet(CustomModelViewSet):
# """list: 专利台账登记
# 专利台账登记
# """
# queryset = PatentRecord.objects.all()
# serializer_class = PatentRecordSerializer
# select_related_fields = ["patent"]
# filterset_fields = ["title", "volume_number","inventors"]
# ordering = ["-create_time", "title", "type"]
# search_fields = ["title", "volume_number", "inventors"]
# def get_queryset(self):
# qs = super().get_queryset()
# patent_type = self.request.query_params.get('patent_type', None)
# if patent_type:
# qs = qs.filter(patent__type=patent_type)
# return qs
# @action(detail=False, methods=['get'])
# def patent_name(self, request):
# """获取专利列表"""
# search = request.query_params.get('search', '')
# queryset = PatentInfo.objects.all()
# if search:
# queryset = queryset.filter(name__icontains=search)
# patents = [{'id': patent.id, 'name': patent.name} for patent in queryset]
# return Response(patents)
# class PlatformViewSet(CustomModelViewSet):
# """list: 平台
# 平台
# """
# queryset = Platform.objects.all()
# serializer_class = PlatformSerializer
# filterset_fields = ["name"]
# ordering = ["create_time", "name"]
# class ProjectViewSet(CustomModelViewSet):
# """list: 项目
# 项目
# """
# queryset = Project.objects.all()
# serializer_class = ProjectSerializer
# filterset_fields = ["name"]
# ordering = ["create_time", "name"]
# class PatentRecordViewSet(CustomModelViewSet):
# """list: 专利台账登记
# 专利台账登记
# """
# queryset = PatentRecord.objects.all()
# serializer_class = ProjectMemberSerializer
# filterset_fields = ["patent", "type"]
# ordering = ["create_time", "patent", "type"]
# class PaperRecordViewSet(CustomModelViewSet):
# """list: 论文台账登记
# 论文台账登记
# """
# queryset = PaperRecord.objects.all()
# serializer_class = ProjectMemberSerializer
# filterset_fields = ["index", "title", "paper_code","paper_type", "authors"]
# ordering = ["create_time", "paper", "type"]
# class ProjectApprovalViewSet(CustomModelViewSet):
# """list: 立项审批表
# 立项审批表
# """
# queryset = ProjectApproval.objects.all()
# serializer_class = ProjectApprovalSerializer
# filterset_fields = ["project_start_date"]
# ordering = ["project_start_date"]
# class ProjectInfoViewSet(CustomModelViewSet):
# """list: 项目信息
# 项目信息
# """
# queryset = ProjectInfo.objects.all()
# serializer_class = ProjectInfoSerializer
# filterset_fields = ["serial_number", "name", "platform", "project_source"]
# ordering = ["serial_number", "name"]

View File

@ -82,8 +82,8 @@ class PmService:
queue.append(input_material_id)
# 返回各Route的任务数量
return {route.id: step_production.get(route.id, 0) for route in route_qs}
# 返回各Route的任务数量/默认在输入物料时使用target_quantity
return {route.id: step_production.get(route.id, target_quantity if route.material_in.id == target_materialId else 0) for route in route_qs}
@classmethod
def cal_real_task_count(cls, count: int, rate_list):
@ -211,14 +211,18 @@ class PmService:
if not gjson_item:
raise ParseError("缺少该产品的生产子图")
rqs = Route.get_routes(routeIds=gjson_item["routes"])
if not rqs.exists():
raise ParseError('未配置工艺路线')
Route.validate_dag(product, rqs)
else:
rqs = Route.get_routes(material=product)
if not rqs.exists():
raise ParseError('未配置工艺路线')
Route.validate_dag(product, rqs, check_final=False)
if not rqs.exists():
raise ParseError('未配置工艺路线')
# 通过出材率校正任务数, out_rate 默认为 100
Route.validate_dag(product, rqs)
r_count_dict = cls.cal_x_task_count(product.id, count, rqs)
# 创建小任务

View File

@ -0,0 +1,40 @@
# Generated by Django 3.2.12 on 2025-11-10 02:00
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('system', '0006_auto_20241213_1249'),
('wf', '0004_workflow_view_path2'),
('pum', '0008_auto_20240731_1829'),
]
operations = [
migrations.CreateModel(
name='SupplierAudit',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.CharField(max_length=100, verbose_name='供应商名称')),
('material_name', models.CharField(max_length=100, verbose_name='物料名称')),
('material_cate', models.CharField(max_length=100, verbose_name='物料类别')),
('business_license', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='supplier_audit_business_license', to='system.file', verbose_name='营业执照')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='supplieraudit_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('quality_certificate', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='supplier_audit_quality_certificate', to='system.file', verbose_name='质量证书')),
('survery_form', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='supplier_audit_survey_form', to='system.file', verbose_name='调查表')),
('ticket', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='supplieraudit_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -0,0 +1,44 @@
# Generated by Django 3.2.12 on 2025-12-26 07:29
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('wf', '0006_auto_20251215_1645'),
('pum', '0009_supplieraudit'),
]
operations = [
migrations.CreateModel(
name='QuotationApply',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('customer_name', models.CharField(max_length=50, verbose_name='客户名称')),
('product_name', models.CharField(max_length=50, verbose_name='产品名称')),
('contact_person', models.CharField(max_length=50, verbose_name='联系人')),
('contact_phone', models.CharField(blank=True, max_length=20, null=True, verbose_name='联系电话')),
('receive_address', models.CharField(blank=True, max_length=100, null=True, verbose_name='收件地址')),
('product_spec_quantity', models.TextField(blank=True, null=True, verbose_name='产品规格/数量')),
('quotation_basis', models.TextField(blank=True, null=True, verbose_name='报价依据')),
('suggested_price_calc', models.TextField(blank=True, null=True, verbose_name='建议价格及计算方式')),
('quotation_range', models.CharField(blank=True, max_length=100, null=True, verbose_name='报价区间')),
('quoter', models.CharField(blank=True, max_length=50, null=True, verbose_name='报价人')),
('apply_date', models.DateField(auto_now_add=True, verbose_name='申请日期')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='quotationapply_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('ticket', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='quo_ticket', to='wf.ticket', verbose_name='关联工单')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='quotationapply_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

@ -1,6 +1,7 @@
from django.db import models
from apps.utils.models import CommonBModel, BaseModel, CommonBDModel
from apps.utils.models import CommonBModel, BaseModel, CommonBDModel, CommonADModel
from apps.mtm.models import Material
from apps.wf.models import Ticket
# Create your models here.
@ -15,6 +16,15 @@ class Supplier(CommonBModel):
address = models.CharField('地址', max_length=200, default='', blank=True)
can_outsource = models.BooleanField('是否可外协', default=False)
class SupplierAudit(CommonADModel):
name = models.CharField('供应商名称', max_length=100)
material_name = models.CharField('物料名称', max_length=100)
material_cate = models.CharField('物料类别', max_length=100)
survery_form = models.ForeignKey('system.file', verbose_name='调查表', on_delete=models.SET_NULL, null=True, blank=True, related_name='supplier_audit_survey_form')
business_license = models.ForeignKey('system.file', verbose_name='营业执照', on_delete=models.SET_NULL, null=True, blank=True, related_name='supplier_audit_business_license')
quality_certificate = models.ForeignKey('system.file', verbose_name='质量证书', on_delete=models.SET_NULL, null=True, blank=True, related_name='supplier_audit_quality_certificate')
ticket = models.ForeignKey('wf.ticket', verbose_name='关联工单', on_delete=models.SET_NULL, null=True, blank=True)
class PuPlan(CommonBModel):
"""
@ -97,3 +107,22 @@ class PuPlanItem(CommonBDModel):
null=True, blank=True, related_name='item_puplan')
pu_order = models.ForeignKey(PuOrder, verbose_name='关联采购订单',
on_delete=models.SET_NULL, null=True, blank=True, related_name='puplan_item_puorder')
class QuotationApply(CommonADModel):
"""
TN:报价申请表
"""
customer_name = models.CharField(max_length=50,verbose_name="客户名称")
product_name = models.CharField(max_length=50,verbose_name="产品名称")
contact_person = models.CharField(max_length=50,verbose_name="联系人")
contact_phone = models.CharField(max_length=20,verbose_name="联系电话", null=True, blank=True)
receive_address = models.CharField(max_length=100,verbose_name="收件地址", null=True, blank=True)
product_spec_quantity = models.TextField(verbose_name="产品规格/数量", null=True, blank=True)
quotation_basis = models.TextField(verbose_name="报价依据", null=True, blank=True)
suggested_price_calc = models.TextField(verbose_name="建议价格及计算方式", null=True, blank=True)
quotation_range = models.CharField(max_length=100,verbose_name="报价区间", null=True, blank=True)
quoter = models.CharField(max_length=50,verbose_name="报价人", null=True, blank=True)
apply_date = models.DateField(verbose_name="申请日期",auto_now_add=True)
ticket = models.OneToOneField('wf.ticket', verbose_name='关联工单',
on_delete=models.CASCADE, related_name='quo_ticket', null=True, blank=True)

View File

@ -1,12 +1,14 @@
from rest_framework import serializers
from apps.utils.serializers import CustomModelSerializer
from apps.utils.constants import EXCLUDE_FIELDS_DEPT, EXCLUDE_FIELDS_BASE, EXCLUDE_FIELDS
from rest_framework.exceptions import ValidationError
from rest_framework.exceptions import ValidationError, ParseError
from apps.pum.models import Supplier, PuPlan, PuPlanItem, PuOrder, PuOrderItem
from apps.pum.models import Supplier, PuPlan, PuPlanItem, PuOrder, PuOrderItem, SupplierAudit, QuotationApply
from apps.mtm.serializers import MaterialSerializer, MaterialSimpleSerializer
from django.db import transaction
from .services import PumService
from apps.wf.serializers import TicketSimpleSerializer
from apps.system.serializers import FileSerializer
class SupplierSerializer(CustomModelSerializer):
@ -139,3 +141,27 @@ class AddSerializer(serializers.Serializer):
label='采购订单ID', queryset=PuOrder.objects.all())
pu_planitems = serializers.PrimaryKeyRelatedField(
label='计划明细ID', queryset=PuPlanItem.objects.all(), many=True)
class SupplierAuditSerializer(CustomModelSerializer):
survery_form_ = FileSerializer(source="survery_form", read_only=True)
business_license_ = FileSerializer(source="business_license", read_only=True)
quality_certificate_ = FileSerializer(source="quality_certificate", read_only=True)
ticket_ = TicketSimpleSerializer(source="ticket", read_only=True)
class Meta:
model = SupplierAudit
fields = "__all__"
read_only_fields = EXCLUDE_FIELDS_BASE + ['ticket']
def create(self, validated_data):
name = validated_data["name"]
if Supplier.objects.filter(name=name).exists():
raise ParseError('供应商名称已存在')
return super().create(validated_data)
class QuotationApplySerializer(CustomModelSerializer):
ticket_ = TicketSimpleSerializer(source='ticket', read_only=True)
class Meta:
model = QuotationApply
fields = "__all__"
read_only_fields = EXCLUDE_FIELDS

View File

@ -1,9 +1,9 @@
from rest_framework.exceptions import ValidationError
from apps.pum.models import PuOrderItem, PuPlan, PuPlanItem, PuOrder
from apps.pum.models import PuOrderItem, PuPlan, PuPlanItem, PuOrder, SupplierAudit
from django.db.models import F, Sum
from apps.inm.models import MIO, MIOItem
from rest_framework.exceptions import ParseError
from apps.wf.models import Ticket, Transition
class PumService:
@ -95,3 +95,18 @@ class PumService:
puplan.state = PuPlan.PUPLAN_DONE
puplan.save()
def bind_supplieraudit(ticket: Ticket, transition: Transition, new_ticket_data: dict):
ins = SupplierAudit.objects.get(id=new_ticket_data['t_id'])
if ins.ticket and ins.ticket.id != ticket.id:
raise ParseError('重复创建工单')
ticket_data = ticket.ticket_data
ticket_data.update({
't_model': 'supplier_audit',
't_id': ins.id,
})
ticket.ticket_data = ticket_data
ticket.create_by = ins.create_by
ticket.save()
if ins.ticket is None:
ins.ticket = ticket
ins.save()

View File

@ -1,16 +1,19 @@
from django.urls import path, include
from rest_framework.routers import DefaultRouter
from apps.pum.views import (SupplierViewSet, PuPlanViewSet, PuPlanItemViewSet, PuOrderViewSet, PuOrderItemViewSet)
from apps.pum.views import (SupplierViewSet, PuPlanViewSet, PuPlanItemViewSet, PuOrderViewSet, PuOrderItemViewSet, SupplierAuditViewSet, QuotationApplyViewSet)
# from apps.pum.views import SupplierViewSet, PuPlanViewSet, PuPlanItemViewSet, PuOrderViewSet, PuOrderItemViewSet
API_BASE_URL = 'api/pum/'
HTML_BASE_URL = 'dhtml/pum/'
router = DefaultRouter()
router.register('supplier', SupplierViewSet, basename='supplier')
router.register('supplieraudit', SupplierAuditViewSet, basename='supplieraudit')
router.register('pu_plan', PuPlanViewSet, basename='pu_plan')
router.register('pu_planitem', PuPlanItemViewSet, basename='pu_planitem')
router.register('pu_order', PuOrderViewSet, basename='pu_order')
router.register('pu_orderitem', PuOrderItemViewSet, basename='pu_orderitem')
router.register('quotation', QuotationApplyViewSet, basename='quotation')
urlpatterns = [
path(API_BASE_URL, include(router.urls)),
]

View File

@ -1,8 +1,8 @@
from django.shortcuts import render
from apps.pum.models import Supplier, PuPlan, PuPlanItem, PuOrder, PuOrderItem
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from apps.pum.serializers import (SupplierSerializer, PuPlanSerializer, PuPlanItemSerializer,
PuOrderSerializer, PuOrderItemSerializer, AddSerializer)
from apps.pum.models import Supplier, PuPlan, PuPlanItem, PuOrder, PuOrderItem, SupplierAudit, QuotationApply
from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet, EuModelViewSet
from apps.pum.serializers import (SupplierSerializer, PuPlanSerializer, PuPlanItemSerializer, QuotationApplySerializer,
PuOrderSerializer, PuOrderItemSerializer, AddSerializer, SupplierAuditSerializer)
from rest_framework.exceptions import ParseError, PermissionDenied
from rest_framework.decorators import action
from rest_framework import serializers
@ -11,6 +11,8 @@ from django.db import transaction
from rest_framework.response import Response
from django.utils import timezone
from apps.pum.services import PumService
from apps.wf.mixins import TicketMixin
from apps.wf.models import Ticket
# Create your views here.
@ -30,6 +32,23 @@ class SupplierViewSet(CustomModelViewSet):
raise ParseError('该供应商存在采购订单不可删除')
instance.delete()
class SupplierAuditViewSet(TicketMixin, CustomModelViewSet):
"""
list: 供应商审核
供应商审核
"""
queryset = SupplierAudit.objects.all()
serializer_class = SupplierAuditSerializer
search_fields = ['name', 'material_name', 'material_cate']
workflow_key = "wf_supplieraudit"
@staticmethod
def add_supplier(ticket: Ticket, transition, new_ticket_data: dict):
supplieraudit = SupplierAudit.objects.get(ticket=ticket)
if Supplier.objects.filter(name=supplieraudit.name).exists():
raise ParseError('供应商名称已存在')
Supplier.objects.create(name=supplieraudit.name)
class PuPlanViewSet(CustomModelViewSet):
"""
@ -191,3 +210,16 @@ class PuOrderItemViewSet(CustomModelViewSet):
item.pu_order = puorder
item.save()
return Response()
class QuotationApplyViewSet(TicketMixin, CustomModelViewSet):
"""
list: 报价申请
报价申请
"""
queryset = QuotationApply.objects.all()
serializer_class = QuotationApplySerializer
filterset_fields = ['product_name', 'customer_name','apply_date', 'quoter']
search_fields = ['product_name', 'customer_name','contact_person']
ordering = ['create_time']
workflow_key = "wf_quotation"

View File

@ -0,0 +1,19 @@
# Generated by Django 3.2.12 on 2025-11-26 01:53
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('qm', '0054_alter_ptest_val_xj'),
]
operations = [
migrations.AlterField(
model_name='ftestitem',
name='ftest',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items_ftest', to='qm.ftest', verbose_name='关联检验'),
),
]

View File

@ -385,7 +385,7 @@ class FtestItem(BaseModel):
TN:检测明细
"""
ftest = models.ForeignKey(
Ftest, verbose_name='关联检验', on_delete=models.CASCADE)
Ftest, verbose_name='关联检验', on_delete=models.CASCADE, related_name='items_ftest')
testitem = models.ForeignKey(
TestItem, verbose_name='质检项目', on_delete=models.CASCADE)
test_equip = models.ForeignKey(Equipment, verbose_name='检测设备', on_delete=models.SET_NULL, null=True, blank=True)

View File

@ -347,8 +347,8 @@ class FtestItemProcessSerializer(CustomModelSerializer):
class FtestProcessSerializer(CustomModelSerializer):
test_user_name = serializers.CharField(
source='test_user.name', read_only=True)
ftestitems = FtestItemProcessSerializer(label='检验明细', many=True)
ftestdefects = FtestDefectSerializer(label='缺陷明细', many=True)
ftestitems = FtestItemProcessSerializer(many=True)
ftestdefects = FtestDefectSerializer(many=True)
class Meta:
model = Ftest
@ -430,4 +430,8 @@ class FtestProcessSerializer(CustomModelSerializer):
instance.defect_main = defect_main
instance.is_ok = is_ok
instance.save()
return instance
return instance
class FtestProcessListSerializer(FtestProcessSerializer):
ftestitems = FtestItemProcessSerializer(source='items_ftest', many=True)
ftestdefects = FtestDefectSerializer(source='defects_ftest', many=True)

View File

@ -157,6 +157,43 @@ def ftestwork_submit(ins:FtestWork, user: User):
# 触发批次统计分析
ana_batch_thread(xbatchs=[ins.batch])
def ftestwork_revert(ins: FtestWork):
wm:WMaterial = ins.wm
if wm and ins.need_update_wm:
fwd_qs = FtestworkDefect.objects.filter(ftestwork=ins)
for item in fwd_qs:
item:FtestworkDefect = item
if item.count > 0:
wm.count = wm.count + item.count
wm.save()
wmstate = WMaterial.WM_OK
if item.defect.okcate == Defect.DEFECT_NOTOK:
wmstate = WMaterial.WM_NOTOK
try:
wmx = WMaterial.objects.get(
material=wm.material,
batch=wm.batch,
mgroup=wm.mgroup,
belong_dept=wm.belong_dept,
state=wmstate,
notok_sign=None,
defect=item.defect,
)
except Exception as e:
raise ParseError(f'找不到{item.defect.name}的车间库存: {str(e)}')
wmx.count = wmx.count - item.count
if wmx.count < 0:
raise ParseError("数量不足,撤回失败")
wmx.save()
else:
raise ParseError("该检验工作不支持撤回")
ins.submit_user = None
ins.submit_time = None
ins.save()
# 触发批次统计分析
ana_batch_thread(xbatchs=[ins.batch])
def bind_ftestwork(ticket: Ticket, transition, new_ticket_data: dict):
ins = FtestWork.objects.get(id=new_ticket_data['t_id'])
if ins.submit_time is not None:

View File

@ -17,7 +17,7 @@ from apps.utils.viewsets import CustomGenericViewSet, CustomModelViewSet
from apps.wpm.models import SfLog
from apps.qm.filters import QuaStatFilter, TestItemFilter, FtestWorkFilter, QctFilter, FtestFilter
from django.db import transaction
from apps.qm.services import ftestwork_submit
from apps.qm.services import ftestwork_submit, ftestwork_revert
from apps.wpm.services_2 import ana_batch_thread
from apps.wf.models import State
# Create your views here.
@ -327,4 +327,20 @@ class FtestWorkViewSet(CustomModelViewSet):
ftestwork_submit(ins, request.user)
else:
raise ParseError('该检验工作已提交')
return Response()
@action(methods=['post'], detail=True, perms_map={'post': 'ftestwork.submit'}, serializer_class=Serializer)
@transaction.atomic
def revert(self, request, *args, **kwargs):
"""撤回检验工作
撤回检验工作
"""
ins:FtestWork = self.get_object()
if ins.submit_time:
if self.request.user != ins.submit_user:
raise ParseError('只能由提交人撤回')
ftestwork_revert(ins)
else:
raise ParseError('该检验工作未提交')
return Response()

0
apps/rem/__init__.py Normal file
View File

3
apps/rem/admin.py Normal file
View File

@ -0,0 +1,3 @@
from django.contrib import admin
# Register your models here.

6
apps/rem/apps.py Normal file
View File

@ -0,0 +1,6 @@
from django.apps import AppConfig
class RemConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'apps.rem'

View File

@ -0,0 +1,41 @@
# Generated by Django 3.2.12 on 2026-01-06 01:49
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
('system', '0006_auto_20241213_1249'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Project',
fields=[
('id', models.CharField(editable=False, help_text='主键ID', max_length=20, primary_key=True, serialize=False, verbose_name='主键ID')),
('create_time', models.DateTimeField(default=django.utils.timezone.now, help_text='创建时间', verbose_name='创建时间')),
('update_time', models.DateTimeField(auto_now=True, help_text='修改时间', verbose_name='修改时间')),
('is_deleted', models.BooleanField(default=False, help_text='删除标记', verbose_name='删除标记')),
('name', models.TextField(verbose_name='项目名称')),
('description', models.TextField(verbose_name='项目介绍')),
('start_date', models.DateField(verbose_name='开始日期')),
('end_date', models.DateField(verbose_name='结束日期')),
('participants', models.TextField(blank=True, null=True, verbose_name='项目成员')),
('note', models.TextField(blank=True, null=True, verbose_name='备注')),
('create_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='project_create_by', to=settings.AUTH_USER_MODEL, verbose_name='创建人')),
('files', models.ManyToManyField(blank=True, to='system.File', verbose_name='附件')),
('leader', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, verbose_name='项目负责人')),
('update_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='project_update_by', to=settings.AUTH_USER_MODEL, verbose_name='最后编辑人')),
],
options={
'abstract': False,
},
),
]

View File

15
apps/rem/models.py Normal file
View File

@ -0,0 +1,15 @@
from django.db import models
from apps.utils.models import CommonADModel
from apps.system.models import User, File
# Create your models here.
class Project(CommonADModel):
name = models.TextField("项目名称")
description = models.TextField("项目介绍")
start_date = models.DateField("开始日期")
end_date = models.DateField("结束日期")
leader = models.ForeignKey(User, verbose_name="项目负责人", on_delete=models.CASCADE)
participants = models.TextField("项目成员", blank=True, null=True)
files = models.ManyToManyField(File, verbose_name="附件", blank=True)
note = models.TextField("备注", blank=True, null=True)

Some files were not shown because too many files have changed in this diff Show More