Compare commits

...

206 Commits

Author SHA1 Message Date
liaodanlong
2644df4fd5 sf-制造-工艺外协的采购订单确认后,外协的调拨单的作业详情的预留数量跟完成数量不对 2025-05-14 17:45:42 +08:00
liaodanlong
9392819375 sf-制造-工艺外协的采购订单确认后,外协的调拨单的作业详情的预留数量跟完成数量不对 2025-05-14 16:58:05 +08:00
liaodanlong
ea3d9e5375 sf-制造-工艺外协的采购订单确认后,外协的调拨单的作业详情的预留数量跟完成数量不对 2025-05-14 16:46:29 +08:00
liaodanlong
238840b647 sf-制造-工艺外协的采购订单确认后,外协的调拨单的作业详情的预留数量跟完成数量不对 2025-05-14 14:54:36 +08:00
liaodanlong
c6f06a4c32 外协调拨单就绪条件修改 2025-05-14 14:12:06 +08:00
liaodanlong
5b61a801c5 空值判断 2025-05-14 13:57:57 +08:00
liaodanlong
ed55e36f45 sf-制造-产品-表单详情中-工艺参数显示内容=未归档,未绑定产品的工艺参数 2025-05-14 11:49:14 +08:00
liaodanlong
3763b60758 sf-制造-产品-表单详情中-工艺参数显示内容=未归档,未绑定产品的工艺参数 2025-05-14 10:48:01 +08:00
liaodanlong
50c3b31ece 外协采购单使用采购申请创建出来的采购单 2025-05-14 10:24:21 +08:00
liaodanlong
8afc8bb3a6 外协调拨单只有当制造订单状态变为已编程才能就绪 2025-05-14 10:03:26 +08:00
liaodanlong
3148ce5a15 外协调拨单不同产品的调拨单混合到一起了 2025-05-13 15:49:02 +08:00
liaodanlong
8e726e1bf6 sf-制造-工艺外协的采购订单确认后,外协的调拨单的作业详情的预留数量跟完成数量不对 2025-05-13 15:01:54 +08:00
liaodanlong
4c0f9cfb39 外协工单零件图号零件名称 2025-05-13 11:04:45 +08:00
liaodanlong
a8e4c7d9a0 调拨单报错问题处理 2025-05-13 10:24:09 +08:00
liaodanlong
7501832637 调拨单就绪问题处理 2025-05-13 10:01:58 +08:00
liaodanlong
ce0ead4da6 sf-工序-手动创建工艺可选参数及产品问题 2025-05-12 17:13:52 +08:00
liaodanlong
6d3793ee30 调拨单就绪问题处理 2025-05-12 15:21:10 +08:00
liaodanlong
9e6f3fa864 Merge branch 'refs/heads/develop' into feature/tool_standard_library_process
# Conflicts:
#	sf_manufacturing/models/purchase_order.py
2025-05-12 15:03:25 +08:00
liaodanlong
ed50d7c9a7 调拨单就绪问题处理 2025-05-12 14:55:19 +08:00
liaodanlong
89e23050e6 打印错误日志堆栈信息 2025-05-12 14:54:43 +08:00
胡尧
b7f912453f Accept Merge Request #2107: (feature/6694 -> develop)
Merge Request: 解决代码问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2107?initial=true
2025-05-12 14:48:07 +08:00
胡尧
cab6b6fa2a 解决代码问题 2025-05-12 14:46:15 +08:00
胡尧
35bf954529 增加中控接口调用日志记录 2025-05-12 11:50:35 +08:00
liaodanlong
1040844b0a 采购申请跳转处理 2025-05-12 11:01:51 +08:00
liaodanlong
36a13c04de 采购申请数量与产品数量不一致问题 2025-05-12 10:55:21 +08:00
liaodanlong
f4318bd997 Merge remote-tracking branch 'origin/feature/tool_standard_library_process' into feature/tool_standard_library_process 2025-05-12 10:25:09 +08:00
liaodanlong
d47a30977c 已归档的表面工艺可选参数不进行产品创建 2025-05-12 10:24:22 +08:00
胡尧
ceb38aa483 Accept Merge Request #2106: (feature/tool_standard_library_process -> develop)
Merge Request: 工艺外协代码

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2106
2025-05-12 08:41:26 +08:00
胡尧
11ecad5ef2 解决冲突 2025-05-12 08:41:12 +08:00
管欢
8249d1427f Accept Merge Request #2105: (feature/采购申请优化 -> develop)
Merge Request: 采购申请数量修改过滤取消

Created By: @管欢
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @管欢
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2105
2025-05-09 18:28:56 +08:00
guanhuan
94bcfc0543 采购申请数量修改过滤取消 2025-05-09 18:15:40 +08:00
guanhuan
b4d31c7c4b 采购申请数量修改 2025-05-09 17:50:43 +08:00
胡尧
d7f7bb9a57 Accept Merge Request #2104: (release/release_2.13 -> develop)
Merge Request: 处理特殊表面工艺采购单确认报错的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2104?initial=true
2025-05-09 15:26:50 +08:00
胡尧
ee87e1dacf 处理特殊表面工艺采购单确认报错的问题 2025-05-09 15:26:12 +08:00
胡尧
2f6c41c999 Accept Merge Request #2103: (release/release_2.13 -> develop)
Merge Request: 修改制造目录文件结构

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2103?initial=true
2025-05-09 14:58:09 +08:00
胡尧
d0d4db1555 修改制造目录文件结构 2025-05-09 14:57:35 +08:00
胡尧
62cbb4b796 Merge branch 'develop' into release/release_2.13 2025-05-09 14:08:06 +08:00
胡尧
f040406002 Accept Merge Request #2102: (feature/6694 -> develop)
Merge Request: 确认接收时,将不追溯的产品的完成数量自动填上

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2102?initial=true
2025-05-09 13:43:36 +08:00
胡尧
bfff4ac440 确认接收时,将不追溯的产品的完成数量自动填上 2025-05-09 13:43:10 +08:00
胡尧
a97386c37c Accept Merge Request #2101: (feature/6694 -> develop)
Merge Request: 调整质检单字段

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2101?initial=true
2025-05-09 10:34:59 +08:00
胡尧
18ae46207a 调整质检单字段 2025-05-09 10:34:23 +08:00
胡尧
bacddd2ad8 修改字段翻译 2025-05-09 08:47:33 +08:00
胡尧
dd5794899d Accept Merge Request #2100: (feature/6694 -> develop)
Merge Request: 人工线下返工在不选择重新编程时复制加工图纸

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2100?initial=true
2025-05-08 21:22:21 +08:00
胡尧
e5b730b2ef 人工线下返工在不选择重新编程时复制加工图纸 2025-05-08 21:21:51 +08:00
胡尧
aea158de41 质检单数量约束 2025-05-08 20:32:59 +08:00
胡尧
a933a0ffea Accept Merge Request #2099: (feature/6694 -> develop)
Merge Request: 委外加工的入库单增加采购申请按钮

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2099?initial=true
2025-05-08 20:13:20 +08:00
胡尧
7575424760 委外加工的入库单增加采购申请按钮 2025-05-08 20:12:12 +08:00
胡尧
6c2eb40e6a Accept Merge Request #2098: (feature/6694 -> develop)
Merge Request: 解决制造订单不显示采购申请按钮的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2098?initial=true
2025-05-08 17:48:05 +08:00
胡尧
f10f595fa4 解决制造订单不显示采购申请按钮的问题 2025-05-08 17:47:40 +08:00
胡尧
6d1de42d76 Accept Merge Request #2097: (feature/6694 -> develop)
Merge Request: 修复收料入库单明细不对的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2097?initial=true
2025-05-08 17:30:19 +08:00
胡尧
5dc16c039c 修复收料入库单明细不对的问题 2025-05-08 17:29:41 +08:00
胡尧
c416cdbeed Accept Merge Request #2096: (feature/6694 -> develop)
Merge Request: 取消采购申请合并明细行的代码,会导致其他后续单据的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2096?initial=true
2025-05-08 16:47:22 +08:00
胡尧
18c7b22319 取消采购申请合并明细行的代码,会导致其他后续单据的问题 2025-05-08 16:47:01 +08:00
liaodanlong
b5339046b9 工艺外协代码 2025-05-08 16:42:18 +08:00
胡尧
e0ba222382 Accept Merge Request #2095: (feature/6694 -> develop)
Merge Request: 解决计算字段报错的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2095?initial=true
2025-05-08 15:57:52 +08:00
胡尧
58b00e6442 解决计算字段报错的问题 2025-05-08 15:57:16 +08:00
胡尧
9182dbfb5d Accept Merge Request #2094: (feature/6694 -> develop)
Merge Request: 修复质检的bug

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2094?initial=true
2025-05-08 15:20:48 +08:00
胡尧
27516844af 修复质检的bug 2025-05-08 15:20:08 +08:00
胡尧
99237445ac 修复质检的bug 2025-05-08 15:17:01 +08:00
管欢
9349ca91d3 Accept Merge Request #2093: (feature/采购申请优化 -> develop)
Merge Request: 采购申请明细优化

Created By: @管欢
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @管欢
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2093
2025-05-08 09:18:28 +08:00
liaodanlong
51c517145b 工艺外协代码 2025-05-07 17:17:41 +08:00
guanhuan
c55f3d77bf Merge branch 'refs/heads/feature/采购申请优化' into release/release_2.13 2025-05-07 16:45:18 +08:00
guanhuan
95716c2e3e 采购申请明细优化 2025-05-07 16:44:48 +08:00
胡尧
5f72519dc2 Accept Merge Request #2092: (feature/6694 -> develop)
Merge Request: 增加采购申请对于单件制造非首个制造订单的显示,修改采购申请对于同一个补货组同一个产品的合并

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2092?initial=true
2025-05-07 16:35:22 +08:00
胡尧
c24bba3137 增加采购申请对于单件制造非首个制造订单的显示,修改采购申请对于同一个补货组同一个产品的合并 2025-05-07 16:34:45 +08:00
guanhuan
01bb6fd0aa Merge branch 'refs/heads/feature/采购申请优化' into release/release_2.13 2025-05-07 16:04:47 +08:00
guanhuan
bf4add6b78 采购申请明细优化 2025-05-07 16:02:39 +08:00
胡尧
7d986fe139 Accept Merge Request #2091: (feature/6694 -> develop)
Merge Request: 表面工艺外协工单流程数量按照制造订单的product_uom_qty设置

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2091?initial=true
2025-05-07 13:02:51 +08:00
胡尧
fffbfc21c2 表面工艺外协工单流程数量按照制造订单的product_uom_qty设置 2025-05-07 13:02:13 +08:00
胡尧
6451bfbc42 Accept Merge Request #2090: (feature/6694 -> develop)
Merge Request: 修改判断坯料序列号的逻辑

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2090?initial=true
2025-05-07 11:01:51 +08:00
胡尧
5aa848de53 修改判断坯料序列号的逻辑 2025-05-07 11:01:01 +08:00
liaodanlong
efc4ae31c9 工艺外协代码 2025-05-07 11:00:32 +08:00
liaodanlong
0863238819 Merge branch 'refs/heads/develop' into feature/tool_standard_library_process 2025-05-07 11:00:24 +08:00
胡尧
4f181e5eba Accept Merge Request #2089: (feature/6694 -> develop)
Merge Request: 将成品的追溯复制到product上,将坯料的追溯同成品的追组

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2089
2025-05-07 08:52:19 +08:00
胡尧
2bf43ae9a1 将成品的追溯复制到product上,将坯料的追溯同成品的追组 2025-05-07 08:47:33 +08:00
胡尧
d98d04d4ed Accept Merge Request #2088: (feature/6694 -> develop)
Merge Request: Merge branch 'develop' into feature/6694

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2088
2025-05-06 15:23:05 +08:00
胡尧
602d6678bc Merge branch 'develop' into feature/6694 2025-05-06 15:22:08 +08:00
胡尧
8fd0c4e1f1 修复计算当前货位的逻辑,减少循环次数 2025-05-06 15:21:45 +08:00
管欢
514fd79c3e Accept Merge Request #2087: (feature/采购申请优化 -> develop)
Merge Request: 采购申请明细优化

Created By: @管欢
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @管欢
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2087
2025-04-29 13:58:19 +08:00
liaodanlong
95c25ac7b8 工艺外协代码 2025-04-29 13:39:36 +08:00
guanhuan
21d052e222 采购申请明细优化 2025-04-29 10:40:19 +08:00
胡尧
95e2c2db0d Accept Merge Request #2086: (feature/6694 -> develop)
Merge Request: 修改打印逻辑,先找默认打印机,未找到则直接返回

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2086?initial=true
2025-04-29 10:11:16 +08:00
胡尧
17a29b7b29 修改打印逻辑,先找默认打印机,未找到则直接返回 2025-04-29 10:09:41 +08:00
胡尧
dd745423a1 修改打印逻辑,先找默认打印机,未找到则直接返回 2025-04-29 10:03:09 +08:00
胡尧
a534e5f400 Accept Merge Request #2085: (feature/6694 -> develop)
Merge Request: 删除多余的打印代码

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2085?initial=true
2025-04-28 19:29:01 +08:00
胡尧
4dc7b5857e 删除多余的打印代码 2025-04-28 19:28:31 +08:00
胡尧
dc679c46cc Accept Merge Request #2084: (feature/6694 -> develop)
Merge Request: 简化打印代码

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2084?initial=true
2025-04-28 19:18:26 +08:00
胡尧
8ccf6cc365 简化打印代码 2025-04-28 19:18:02 +08:00
胡尧
f8457ae66b Accept Merge Request #2083: (feature/6694 -> develop)
Merge Request: 简化打印代码

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2083?initial=true
2025-04-28 19:12:31 +08:00
胡尧
12c8641f2e 简化打印代码 2025-04-28 19:12:08 +08:00
胡尧
f42938f668 Accept Merge Request #2082: (feature/6694 -> develop)
Merge Request: 增加打印日志

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2082?initial=true
2025-04-28 18:28:49 +08:00
胡尧
a856c5cbf7 增加打印日志 2025-04-28 18:28:21 +08:00
胡尧
6411e79904 Accept Merge Request #2081: (feature/6694 -> develop)
Merge Request: 增加日志

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2081?initial=true
2025-04-28 16:42:39 +08:00
胡尧
946f08c479 增加日志 2025-04-28 16:42:20 +08:00
胡尧
4a198639ec Accept Merge Request #2080: (feature/6694 -> develop)
Merge Request: Merge branch 'develop' into feature/6694

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2080?initial=true
2025-04-28 15:47:42 +08:00
胡尧
234812bb40 Merge branch 'develop' into feature/6694 2025-04-28 15:47:15 +08:00
胡尧
dd43e31c3c 修改打印pdf数据 2025-04-28 15:46:35 +08:00
廖丹龙
2f5b0281c3 Accept Merge Request #2079: (feature/process_outsourcing_code_stripping -> develop)
Merge Request: Merge remote-tracking branch 'origin/release/release_2.12' into release/release_2.12

Created By: @廖丹龙
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2079
2025-04-28 15:26:22 +08:00
liaodanlong
d4cf2a9d17 Merge remote-tracking branch 'origin/release/release_2.12' into release/release_2.12 2025-04-28 09:43:49 +08:00
liaodanlong
ecf5dcf2f2 sf .r-采购-采购订单-坯料委外加工生成的采购申请创建采购订单的类型不正确 2025-04-28 09:43:25 +08:00
胡尧
848e8a5fa8 merge branch 'develop' into release/release_2.12 2025-04-28 09:12:50 +08:00
胡尧
cc38383e32 Accept Merge Request #2078: (feature/6694 -> develop)
Merge Request: 修复bug

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2078?initial=true
2025-04-28 09:12:31 +08:00
胡尧
39de4e5ea1 修复bug 2025-04-28 09:12:09 +08:00
胡尧
8b6c904dae Merge branch 'develop' into release/release_2.12 2025-04-28 08:57:10 +08:00
胡尧
a63f2d28f6 Accept Merge Request #2077: (feature/6694 -> develop)
Merge Request: 修改字体获取目录

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2077?initial=true
2025-04-28 08:56:49 +08:00
liaodanlong
08812f169e Merge remote-tracking branch 'origin/release/release_2.12' into release/release_2.12 2025-04-27 17:20:40 +08:00
liaodanlong
ce79016bef 返工未申请重新编程,新工单复制程序文件问题处理 2025-04-27 17:20:15 +08:00
胡尧
fef960f7e8 修改字体获取目录 2025-04-27 16:29:39 +08:00
胡尧
425c9fb64b Merge branch 'develop' into release/release_2.12 2025-04-27 15:32:52 +08:00
胡尧
fc9a58c0c3 Accept Merge Request #2076: (feature/6694 -> develop)
Merge Request: 退回字体处理

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2076?initial=true
2025-04-27 15:32:31 +08:00
胡尧
ed90ad34e6 退回字体处理 2025-04-27 15:32:08 +08:00
胡尧
5662094ec4 Accept Merge Request #2075: (feature/6694 -> develop)
Merge Request: 屏蔽删除文件代码

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2075?initial=true
2025-04-27 15:25:42 +08:00
胡尧
404c56e134 屏蔽删除文件代码 2025-04-27 15:24:28 +08:00
胡尧
9ee614aa10 Accept Merge Request #2074: (feature/6694 -> develop)
Merge Request: 修改上传ftp代码

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2074?initial=true
2025-04-27 15:11:46 +08:00
胡尧
57789dc5a5 修改上传ftp代码 2025-04-27 15:10:22 +08:00
胡尧
52d436909b Merge branch 'develop' into release/release_2.12 2025-04-27 14:37:18 +08:00
胡尧
3a760a66e1 Accept Merge Request #2073: (feature/6694 -> develop)
Merge Request: 退回字体修改

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2073
2025-04-27 14:36:57 +08:00
胡尧
72415d633c 退回字体修改 2025-04-27 14:36:13 +08:00
胡尧
5c67a8c190 Accept Merge Request #2072: (feature/6694 -> develop)
Merge Request: 修改验证规则

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2072?initial=true
2025-04-27 14:27:08 +08:00
胡尧
46ba682848 修改验证规则 2025-04-27 14:26:31 +08:00
胡尧
6b38062e87 解决冲突 2025-04-27 14:15:26 +08:00
胡尧
0945754736 Accept Merge Request #2071: (feature/6694 -> develop)
Merge Request: 解决pdf上数字乱码的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2071?initial=true
2025-04-27 14:13:45 +08:00
胡尧
644ff967e5 解决pdf上数字乱码的问题 2025-04-27 14:10:29 +08:00
liaodanlong
5f79d2038c 工艺外协采购单展示问题 2025-04-27 11:49:56 +08:00
liaodanlong
defd779279 返工 不重新编程 cnc加工工单没有数据问题 2025-04-27 11:35:29 +08:00
liaodanlong
e2e820267e 字符串拼接问题处理 2025-04-27 11:04:09 +08:00
liaodanlong
94f179a6d6 工艺外协代码回退 2025-04-27 10:46:48 +08:00
liaodanlong
bf9f4c1276 工艺外协代码回退 2025-04-27 10:20:45 +08:00
liaodanlong
51a633594f 工艺外协代码回退 2025-04-27 09:24:39 +08:00
liaodanlong
7d7c7b0fcf 工艺外协代码回退 2025-04-27 09:09:48 +08:00
liaodanlong
d88ac22b7c Merge branch 'refs/heads/develop' into release/release_2.12 2025-04-27 09:02:27 +08:00
廖丹龙
1f4e1c11c8 Accept Merge Request #2070: (feature/process_outsourcing_code_stripping -> develop)
Merge Request: Merge remote-tracking branch 'origin/develop' into develop

Created By: @廖丹龙
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2070
2025-04-27 09:00:49 +08:00
liaodanlong
9f1beb4013 Merge remote-tracking branch 'origin/develop' into develop
# Conflicts:
#	sf_manufacturing/models/mrp_production.py
#	sf_manufacturing/models/purchase_order.py
#	sf_sale/models/sale_order.py
2025-04-27 08:50:45 +08:00
liaodanlong
f864466987 Merge branch 'refs/heads/feature/process_outsourcing_code_stripping' into develop 2025-04-27 08:49:13 +08:00
liaodanlong
9cf70cc54c 工艺外协代码回退 2025-04-26 14:42:38 +08:00
胡尧
82bd50cb97 修改文件传输规则 2025-04-25 17:30:51 +08:00
胡尧
4bce26721d Merge branch 'develop' into release/release_2.12 2025-04-25 16:41:08 +08:00
胡尧
3fb4e7c413 Accept Merge Request #2069: (feature/6694 -> develop)
Merge Request: 调整字体大小

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2069?initial=true
2025-04-25 16:40:24 +08:00
胡尧
a7ab8679f4 调整字体大小 2025-04-25 16:39:51 +08:00
胡尧
ca9a91e30a 修改程序单二维码下方文字 2025-04-25 16:07:34 +08:00
胡尧
314d738412 接口授权 2025-04-25 15:59:11 +08:00
胡尧
699e03ccda Accept Merge Request #2068: (release/release_2.12 -> develop)
Merge Request: 解决冲突

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2068?initial=true
2025-04-25 15:58:27 +08:00
管欢
8f0ade7b43 Accept Merge Request #2067: (feature/页面显示优化 -> develop)
Merge Request: 紧急采购默认为是

Created By: @管欢
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @管欢
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2067
2025-04-25 15:55:38 +08:00
胡尧
50bc8786e8 解决冲突 2025-04-25 15:50:40 +08:00
胡尧
0777e63bc7 修复字符集出错的问题 2025-04-25 15:49:41 +08:00
guanhuan
128bebf338 紧急采购默认为是 2025-04-25 14:57:56 +08:00
廖丹龙
7a71077aa7 Accept Merge Request #2066: (feature/tool_standard_library_process -> develop)
Merge Request: 排程

Created By: @廖丹龙
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2066
2025-04-25 14:41:01 +08:00
liaodanlong
10a1d43a17 排程 2025-04-25 14:39:58 +08:00
胡尧
87d351e9e9 Accept Merge Request #2065: (feature/6694 -> develop)
Merge Request: 增加接口授权

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2065?initial=true
2025-04-25 14:09:36 +08:00
胡尧
d2daae1a8f 增加接口授权 2025-04-25 14:09:13 +08:00
胡尧
5997c24895 Accept Merge Request #2064: (feature/6694 -> develop)
Merge Request: 增加传输文件列表返回

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2064?initial=true
2025-04-25 14:05:10 +08:00
胡尧
df53989f22 增加传输文件列表返回 2025-04-25 14:04:48 +08:00
胡尧
9bab687080 Accept Merge Request #2063: (feature/6609 -> develop)
Merge Request: 修改机台二维码为机台编码

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2063?initial=true
2025-04-25 13:45:29 +08:00
胡尧
a5ac8b8b84 修改机台二维码为机台编码 2025-04-25 13:40:18 +08:00
胡尧
2cde398e11 Accept Merge Request #2062: (feature/6609 -> develop)
Merge Request: 修改机台二维码为机台编码

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2062?initial=true
2025-04-25 13:38:29 +08:00
胡尧
88026fea5d 修改机台二维码为机台编码 2025-04-25 13:37:12 +08:00
liaodanlong
443a21a0cc 工序外协需求 2025-04-25 11:46:05 +08:00
马广威
e14646a6fc Accept Merge Request #2061: (feature/制造功能优化 -> develop)
Merge Request: 调整dashboard取值

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2061?initial=true
2025-04-25 11:35:31 +08:00
mgw
6a920be6d1 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-04-25 11:34:51 +08:00
mgw
3811079a7f 调整alarm_start_time为时间戳 2025-04-25 11:34:34 +08:00
mgw
ad8e0b6af0 调整当日故障时长取值 2025-04-25 11:25:38 +08:00
廖丹龙
04cb910803 Accept Merge Request #2060: (feature/tool_standard_library_process -> develop)
Merge Request: 【sf.t-计划】在排程单详情页选择时间段进行排程时未校验日历有效工作时间

Created By: @廖丹龙
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2060
2025-04-25 11:18:43 +08:00
liaodanlong
42292818af Merge branch 'refs/heads/develop' into feature/tool_standard_library_process
# Conflicts:
#	sf_plan/models/custom_plan.py
2025-04-25 11:17:48 +08:00
liaodanlong
bcafd9cf38 【sf.t-计划】在排程单详情页选择时间段进行排程时未校验日历有效工作时间 2025-04-25 11:16:23 +08:00
mgw
12ebd87f1d Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-04-25 10:14:00 +08:00
mgw
bdef852b98 调整当日在线时长取值为今天之前最后一次开机时长 2025-04-25 10:13:42 +08:00
胡尧
1d5fb747d4 Accept Merge Request #2059: (feature/6609 -> develop)
Merge Request: 解决更新出现字段未找到的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2059?initial=true
2025-04-25 10:09:21 +08:00
胡尧
8116e4f97d 解决更新出现字段未找到的问题 2025-04-25 10:08:03 +08:00
mgw
e3e5fcc378 调整当日故障时长 2025-04-25 09:42:45 +08:00
胡尧
879b5492db Accept Merge Request #2058: (feature/6609 -> develop)
Merge Request: 产品详情增加只有成品类型才显示模型ID

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2058
2025-04-25 09:19:17 +08:00
胡尧
27b9a4f982 解决冲突 2025-04-25 09:19:00 +08:00
胡尧
94007bae2b 产品详情增加只有成品类型才显示模型ID 2025-04-25 09:16:40 +08:00
mgw
bf92028027 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-04-24 17:27:40 +08:00
mgw
2b47e566d3 调整周期字段为当日0时间 2025-04-24 17:27:20 +08:00
胡尧
5aa2f1aa18 Accept Merge Request #2057: (feature/6694 -> develop)
Merge Request: 解决排程报错的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2057?initial=true
2025-04-24 17:08:10 +08:00
胡尧
b7128ba81a 解决排程报错的问题 2025-04-24 17:07:30 +08:00
胡尧
49546f9d08 Accept Merge Request #2056: (feature/6694 -> develop)
Merge Request: 修改参数类型判断

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2056?initial=true
2025-04-24 15:13:40 +08:00
胡尧
6959bd9a09 修改参数类型判断 2025-04-24 15:13:21 +08:00
禹翔辉
3a2babf2d5 Accept Merge Request #2055: (feature/工单排序_1 -> develop)
Merge Request: Merge branch 'feature/工单排序' into feature/工单排序_1

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2055
2025-04-24 15:11:23 +08:00
yuxianghui
d7d094c84d Merge branch 'feature/工单排序' into feature/工单排序_1 2025-04-24 15:05:57 +08:00
yuxianghui
a06e24583d 添加工单先按工序排序,再按创建时间排序 2025-04-24 15:04:04 +08:00
胡尧
0cbd830901 Accept Merge Request #2053: (feature/6694 -> develop)
Merge Request: 修改模型id为模型ID,增加机台判断

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2053?initial=true
2025-04-24 14:59:12 +08:00
胡尧
4b29def105 修改模型id为模型ID,增加机台判断 2025-04-24 14:58:50 +08:00
胡尧
582abb3f2e Accept Merge Request #2052: (feature/6694 -> develop)
Merge Request: 去掉产品form重复的字段

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2052?initial=true
2025-04-24 14:38:07 +08:00
胡尧
40137ba69c 去掉产品form重复的字段 2025-04-24 14:31:22 +08:00
胡尧
804f6a82b4 Accept Merge Request #2051: (feature/6694 -> develop)
Merge Request: 销售订单,制造订单视图中加入模型id

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2051?initial=true
2025-04-24 14:15:58 +08:00
胡尧
d16d47dfbe 销售订单,制造订单视图中加入模型id 2025-04-24 14:13:10 +08:00
胡尧
41cf9d5474 Accept Merge Request #2050: (feature/6694 -> develop)
Merge Request: 修改扫码报错信息

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2050?initial=true
2025-04-24 11:42:52 +08:00
胡尧
59aa6b4f10 修改扫码报错信息 2025-04-24 11:42:16 +08:00
胡尧
a759106fdc Accept Merge Request #2049: (feature/6694 -> develop)
Merge Request: 调整打印代码结构

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2049?initial=true
2025-04-24 11:08:45 +08:00
胡尧
8bb101c6b2 调整打印代码结构 2025-04-24 11:08:02 +08:00
胡尧
f02044b513 Accept Merge Request #2048: (feature/6694 -> develop)
Merge Request: 调整机台二维码使用A4打印机

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2048?initial=true
2025-04-24 09:59:29 +08:00
胡尧
3d937b85c9 调整机台二维码使用A4打印机 2025-04-24 09:59:05 +08:00
胡尧
5a61b3b459 调整机台二维码使用A4打印机 2025-04-24 09:58:27 +08:00
胡尧
afccb5ee6a Accept Merge Request #2047: (feature/6694 -> develop)
Merge Request: 修改机台二维码为ID

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2047?initial=true
2025-04-24 09:19:13 +08:00
胡尧
2b0648d9bc 修改机台二维码为ID 2025-04-24 09:18:49 +08:00
胡尧
8ea3487044 Accept Merge Request #2046: (feature/6694 -> develop)
Merge Request: 增加import

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2046?initial=true
2025-04-24 08:59:38 +08:00
胡尧
b24ed5fe4c 增加import 2025-04-24 08:59:07 +08:00
胡尧
b801b265c3 Accept Merge Request #2045: (feature/6694 -> develop)
Merge Request: 增加日志界面

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2045?initial=true
2025-04-23 17:51:41 +08:00
胡尧
27a67167fe 增加日志界面 2025-04-23 17:51:09 +08:00
胡尧
8fa9534b4e 增加接口请求日志,修改报工接口获取机台id 2025-04-23 17:39:42 +08:00
胡尧
db745e46b6 Accept Merge Request #2044: (feature/6609 -> develop)
Merge Request: 解决从制造订单跳转到产品无法显示模型id的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2044?initial=true
2025-04-23 16:59:51 +08:00
胡尧
f598b6c71c 解决从制造订单跳转到产品无法显示模型id的问题 2025-04-23 16:58:59 +08:00
胡尧
dccb0b3fb0 Accept Merge Request #2043: (feature/6763 -> develop)
Merge Request: 修改机台二维码为机台ID

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2043?initial=true
2025-04-23 16:16:50 +08:00
胡尧
ac09794b10 Accept Merge Request #2042: (feature/6763 -> develop)
Merge Request: 解决模块更新报错问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2042?initial=true
2025-04-23 16:00:13 +08:00
胡尧
bff0ff9401 增加日志 2025-04-23 10:18:49 +08:00
66 changed files with 1352 additions and 363 deletions

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import models

View File

@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
{
'name': '机企猫 打印模块',
'version': '1.0',
'summary': """ 包含机台二维码,程序单打印等 """,
'author': '机企猫',
'website': 'https://www.jikimo.com',
'category': '机企猫',
'depends': ['sf_manufacturing', 'sf_maintenance', 'base_report_to_printer'],
'data': [
'views/maintenance_views.xml',
],
'application': True,
'installable': True,
'auto_install': False,
'license': 'LGPL-3',
}

View File

@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
from . import jikimo_printing
from . import maintenance_printing
from . import workorder_printing

View File

@@ -0,0 +1,87 @@
from io import BytesIO
import qrcode
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from PIL import Image
import logging
from reportlab.lib.utils import ImageReader
from odoo import models, fields, api
import base64
_logger = logging.getLogger(__name__)
class JikimoPrinting(models.AbstractModel):
_name = 'jikimo.printing'
def print_qr_code(self, data):
"""
打印二维码
"""
printer = self.env['printing.printer'].get_default()
if not printer:
_logger.error("未找到默认打印机")
return False
# 生成二维码
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(data)
qr.make(fit=True)
qr_image = qr.make_image(fill_color="black", back_color="white")
# 将PIL Image转换为reportlab可用的格式
temp_image = BytesIO()
qr_image.save(temp_image, format="PNG")
temp_image.seek(0)
# 创建PDF
pdf_buffer = BytesIO()
c = canvas.Canvas(pdf_buffer, pagesize=A4)
# 计算位置
a4_width, a4_height = A4
qr_width = 200
qr_height = 200
x = (a4_width - qr_width) / 2
y = (a4_height - qr_height) / 2
# 直接从BytesIO绘制图片
c.drawImage(ImageReader(Image.open(temp_image)), x, y, width=qr_width, height=qr_height)
c.save()
# 获取PDF内容并打印
pdf_content = pdf_buffer.getvalue()
# _logger.info(f"打印内容: {pdf_content}")
printer.print_document(report=None, content=pdf_content, doc_format='pdf')
# 清理资源
pdf_buffer.close()
temp_image.close()
return True
def print_pdf(self, pdf_data):
"""
打印PDF
"""
printer = self.env['printing.printer'].get_default()
if not printer:
_logger.error("未找到默认打印机")
return False
pdf_data_str = pdf_data.decode('ascii', errors='ignore')
decoded_data = base64.b64decode(pdf_data_str)
# 处理二进制数据
pdf_buffer = BytesIO()
pdf_buffer.write(decoded_data)
pdf_buffer.seek(0)
# 获取PDF内容
pdf_content = pdf_buffer.getvalue()
printer.print_document(report=None, content=pdf_content, doc_format='pdf')
# 清理资源
pdf_buffer.close()
_logger.info("成功打印PDF")
return True

View File

@@ -0,0 +1,69 @@
from odoo import models, fields, api
class MaintenancePrinting(models.Model):
_inherit = 'maintenance.equipment'
def print_single_method(self):
print('self.name========== %s' % self.name)
self.ensure_one()
# maintenance_equipment_id = self.id
# # host = "192.168.50.110" # 可以根据实际情况修改
# # port = 9100 # 可以根据实际情况修改
# # 获取默认打印机配置
# printer_config = self.env['printer.configuration'].sudo().search([('model', '=', self._name)], limit=1)
# if not printer_config:
# raise UserError('请先配置打印机')
# host = printer_config.printer_id.ip_address
# port = printer_config.printer_id.port
# self.print_qr_code(maintenance_equipment_id, host, port)
# 切换成A4打印机
try:
self.env['jikimo.printing'].print_qr_code(self.MTcode)
except Exception as e:
raise UserError(f"打印失败: {str(e)}")
# def generate_zpl_code(self, code):
# """生成ZPL代码用于打印二维码标签
# Args:
# code: 需要编码的内容
# Returns:
# str: ZPL指令字符串
# """
# zpl_code = "^XA\n" # 开始ZPL格式
# # 设置打印参数
# zpl_code += "^LH0,0\n" # 设置标签起始位置
# zpl_code += "^CI28\n" # 设置中文编码
# zpl_code += "^PW400\n" # 设置打印宽度为400点
# zpl_code += "^LL300\n" # 设置标签长度为300点
# # 打印标题
# zpl_code += "^FO10,20\n" # 设置标题位置
# zpl_code += "^A0N,30,30\n" # 设置字体大小
# zpl_code += "^FD机床二维码^FS\n" # 打印标题文本
# # 打印二维码
# zpl_code += "^FO50,60\n" # 设置二维码位置
# zpl_code += f"^BQN,2,8\n" # 设置二维码参数:模式2,放大倍数8
# zpl_code += f"^FDLA,{code}^FS\n" # 二维码内容
# # 打印编码文本
# zpl_code += "^FO50,220\n" # 设置编码文本位置
# zpl_code += "^A0N,25,25\n" # 设置字体大小
# zpl_code += f"^FD编码: {code}^FS\n" # 打印编码文本
# # 打印日期
# zpl_code += "^FO50,260\n"
# zpl_code += "^A0N,20,20\n"
# zpl_code += f"^FD打印日期: {fields.Date.today()}^FS\n"
# zpl_code += "^PQ1\n" # 打印1份
# zpl_code += "^XZ\n" # 结束ZPL格式
# return zpl_code

View File

@@ -1,8 +1,6 @@
import logging
from io import BytesIO
from odoo import models, fields, api
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
@@ -21,12 +19,8 @@ class MrpWorkorder(models.Model):
if pdf_data:
try:
# 执行打印
printer = self.env['printing.printer'].get_default()
printer.print_document(report=None, content = pdf_data, doc_format='pdf')
self.env['jikimo.printing'].print_pdf(pdf_data)
wo.production_id.product_id.is_print_program = True
_logger.info(f"工单 {wo.name} 的PDF已成功打印")
except Exception as e:
_logger.error(f"工单 {wo.name} 的PDF打印失败: {str(e)}")

View File

@@ -0,0 +1,19 @@
<?xml version="1.0"?>
<odoo>
<record id="sf_maintenance_equipment_view_form_qrcode_print" model="ir.ui.view">
<field name="name">sf_equipment.form</field>
<field name="model">maintenance.equipment</field>
<field name="inherit_id" ref="sf_maintenance.sf_hr_equipment_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='qr_code_image']" position="after">
<label for="print_single_method"/>
<div class="col-12 col-lg-6 o_setting_box" style="white-space: nowrap">
<button type="object" class="oe_highlight" name='print_single_method' string="打印机床二维码"
attrs="{'invisible': [('equipment_type', '!=', '机床')]}"/>
</div>
</xpath>
</field>
</record>
</odoo>

View File

@@ -12,12 +12,14 @@
'views/mrp_production.xml',
'views/purchase_request_view.xml',
'wizard/purchase_request_line_make_purchase_order_view.xml',
'views/purchase_request_line_view.xml',
'views/stock_picking_views.xml',
],
# 'assets': {
# 'web.assets_backend': [
# 'jikimo_purchase_request/static/src/**/*'
# ],
# },
'assets': {
'web.assets_backend': [
'jikimo_purchase_request/static/src/**/*'
],
},
'application': True,
'installable': True,
'auto_install': False,

View File

@@ -1043,7 +1043,7 @@ msgstr "询价单"
#. module: purchase_request
#: model:ir.model.fields,field_description:purchase_request.field_purchase_request_line__purchased_qty
msgid "RFQ/PO Qty"
msgstr ""
msgstr "已订购数"
#. module: purchase_request
#. odoo-python

View File

@@ -5,3 +5,4 @@ from . import sale_order
from . import mrp_production
from . import purchase_order
from . import stock_rule
from . import stock_picking

View File

@@ -9,18 +9,32 @@ class MrpProduction(models.Model):
@api.depends('state')
def _compute_pr_mp_count(self):
for item in self:
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name), ('is_subcontract', '!=', 'True')])
if pr_ids:
item.pr_mp_count = len(pr_ids)
else:
item.pr_mp_count = 0
# if item.product_id.product_tmpl_id.single_manufacturing == True and not item.is_remanufacture:
# first_order = self.env['mrp.production'].search(
# [('origin', '=', item.origin), ('product_id', '=', item.product_id.id)], limit=1, order='id asc')
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', first_order.name)])
# item.pr_mp_count = len(pr_ids)
# else:
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name)])
# item.pr_mp_count = len(pr_ids)
# 由于采购申请合并了所有销售订单行的采购,所以不区分产品
first_mp = self.env['mrp.production'].search(
[('origin', '=', item.origin)], limit=1, order='id asc')
pr_ids = self.env['purchase.request'].sudo().search(
[('origin', 'like', first_mp.name), ('is_subcontract', '!=', 'True')])
item.pr_mp_count = len(pr_ids)
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name), ('is_subcontract', '!=', 'True')])
def action_view_pr_mp(self):
"""
采购请求
"""
self.ensure_one()
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name),('is_subcontract', '!=', True)])
first_mp = self.env['mrp.production'].search(
[('origin', '=', self.origin)], limit=1, order='id asc')
pr_ids = self.env['purchase.request'].sudo().search(
[('origin', 'like', first_mp.name), ('is_subcontract', '!=', 'True')])
action = {
'res_model': 'purchase.request',
'type': 'ir.actions.act_window',
@@ -33,7 +47,7 @@ class MrpProduction(models.Model):
else:
action.update({
'name': _("%s生成采购请求单", self.name),
'domain': [('id', 'in', pr_ids)],
'domain': [('id', 'in', pr_ids.ids)],
'view_mode': 'tree,form',
})
return action

View File

@@ -47,6 +47,19 @@ class PurchaseRequestLine(models.Model):
('outsourcing', "委外加工"),
], string='供货方式', compute='_compute_supply_method', store=True)
purchase_request_count = fields.Integer(string='采购申请数量', compute='_compute_purchase_request_count', readonly=True)
purchase_count = fields.Integer(string="采购订单数量", compute="_compute_purchase_count", readonly=True)
@api.depends("purchase_lines")
def _compute_purchase_count(self):
for rec in self:
rec.purchase_count = len(rec.mapped("purchase_lines.order_id"))
@api.depends('request_id')
def _compute_purchase_request_count(self):
for order in self:
order.purchase_request_count = len(order.request_id)
@api.depends('origin')
def _compute_supply_method(self):
for prl in self:
@@ -98,3 +111,36 @@ class PurchaseRequestLine(models.Model):
else:
record.part_number = record.product_id.part_number
record.part_name = record.product_id.part_name
def _compute_qty_to_buy(self):
for pr in self:
qty_to_buy = sum(pr.mapped("product_qty"))
if pr.purchase_count > 0:
qty_to_buy -= sum(pr.mapped("purchase_lines").filtered(lambda po: po.state != 'cancel').mapped(
"product_qty"))
pr.qty_to_buy = qty_to_buy > 0.0
pr.pending_qty_to_receive = qty_to_buy
def action_view_purchase_request(self):
action = self.env["ir.actions.actions"]._for_xml_id("purchase_request.purchase_request_form_action")
action.update({
'res_id': self.request_id.id,
'views': [[False, 'form']],
})
return action
def action_view_purchase_order(self):
action = self.env["ir.actions.actions"]._for_xml_id("purchase.purchase_rfq")
lines = self.mapped("purchase_lines.order_id")
if len(lines) > 1:
action["domain"] = [("id", "in", lines.ids)]
elif lines:
action["views"] = [
(self.env.ref("purchase.purchase_order_form").id, "form")
]
action["res_id"] = lines.id
origin_context = ast.literal_eval(action['context'])
if 'search_default_draft' in origin_context:
origin_context.pop('search_default_draft')
action['context'] = origin_context
return action

View File

@@ -0,0 +1,35 @@
from odoo import fields, api, models, _
class StockPicking(models.Model):
_inherit = "stock.picking"
purchase_request_count = fields.Integer('采购订单数量', compute='_compute_purchase_request')
@api.depends('name')
def _compute_purchase_request(self):
for record in self:
purchase_request_ids = self.env['purchase.request'].search([('origin', '=', record.name)])
record.purchase_request_count = len(purchase_request_ids)
def action_view_purchase_request(self):
self.ensure_one()
purchase_request_ids = self.env['purchase.request'].search([('origin', '=', self.name)])
action = {
'res_model': 'purchase.request',
'type': 'ir.actions.act_window',
}
if len(purchase_request_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': purchase_request_ids[0].id,
})
else:
action.update({
'name': _("%s生成采购请求单", self.name),
'domain': [('id', 'in', purchase_request_ids.ids)],
'view_mode': 'tree,form',
})
return action

View File

@@ -1,4 +1,5 @@
from odoo import api, fields, models
from collections import defaultdict
class StockRule(models.Model):
@@ -44,7 +45,40 @@ class StockRule(models.Model):
purchase_request_line_model.create(request_line_data)
def _run_buy(self, procurements):
res = super(StockRule, self)._run_buy(procurements)
# 如果补货组相同,并且产品相同,则合并
procurements_dict = defaultdict()
for procurement, rule in procurements:
if (procurement.product_id.id, procurement.values['group_id'], rule.id) not in procurements_dict:
procurements_dict[(procurement.product_id.id, procurement.values['group_id'], rule.id)] = {
'product_id': procurement.product_id,
'product_qty': procurement.product_qty,
'product_uom': procurement.product_uom,
'location_id': procurement.location_id,
'name': procurement.name,
'origin': procurement.origin,
'company_id': procurement.company_id,
'values': procurement.values,
'rule': rule
}
else:
procurements_dict[(procurement.product_id.id, procurement.values['group_id'], rule.id)]['product_qty'] += procurement.product_qty
procurements_dict[(procurement.product_id.id, procurement.values['group_id'], rule.id)]['values']['move_dest_ids'] |= procurement.values['move_dest_ids']
new_procurements = []
for k, p in procurements_dict.items():
new_procurements.append((
self.env['procurement.group'].Procurement(
product_id=p['product_id'],
product_qty=p['product_qty'],
product_uom=p['product_uom'],
location_id=p['location_id'],
name=p['name'],
origin=p['origin'],
company_id=p['company_id'],
values=p['values']
), p['rule'])
)
res = super(StockRule, self)._run_buy(new_procurements)
# 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved
origins = list(set([procurement[0].origin for procurement in procurements]))
for origin in origins:

View File

@@ -0,0 +1,3 @@
th[data-name=keep_description] {
min-width: 220px;
}

View File

@@ -0,0 +1,22 @@
<odoo>
<record id="purchase_request_line_form_sf" model="ir.ui.view">
<field name="name">purchase.request.line.sf.form</field>
<field name="model">purchase.request.line</field>
<field name="inherit_id" ref="purchase_request.purchase_request_line_form"/>
<field name="arch" type="xml">
<xpath expr="//h1" position="before">
<div class="oe_button_box" name="button_box">
<button type="object" name="action_view_purchase_request" class="oe_stat_button"
icon="fa-file">
<field name="purchase_request_count" widget="statinfo" string="采购申请"/>
</button>
<button type="object" name="action_view_purchase_order" class="oe_stat_button"
attrs="{'invisible': [('purchase_count', '=', 0)]}" icon="fa-shopping-cart">
<field name="purchase_count" widget="statinfo" string="采购订单"/>
</button>
</div>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="stock_pikcing_inherited_form_jikimo_purchase_request" model="ir.ui.view">
<field name="name">stock.pikcing.inherited.form.jikimo.purchase.request</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']/button" position="before">
<button class="oe_stat_button" name="action_view_purchase_request" type="object" icon="fa-credit-card"
attrs="{'invisible': [('purchase_request_count', '=', 0)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="purchase_request_count"/>
</span>
<span class="o_stat_text">采购申请</span>
</div>
</button>
</xpath>
</field>
</record>
</odoo>

View File

@@ -32,6 +32,7 @@ class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
line.company_id,
line.request_id.origin,
)
# po_data.update({'related_product':line.related_product.id})
purchase = purchase_obj.create(po_data)
# Look for any other PO line in the selected PO with same
@@ -63,6 +64,8 @@ class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
po_line_data = self._prepare_purchase_order_line(purchase, item)
if item.keep_description:
po_line_data["name"] = item.name
if line.related_product:
po_line_data.update({'related_product': line.related_product.id})
po_line = po_line_obj.create(po_line_data)
po_line_product_uom_qty = po_line.product_uom._compute_quantity(
po_line.product_uom_qty, alloc_uom
@@ -101,9 +104,26 @@ class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
# 去掉合并必须同一采购组的限制
pass
def get_items(self, request_line_ids):
request_line_obj = self.env["purchase.request.line"]
items = []
request_lines = request_line_obj.browse(request_line_ids).filtered(lambda line: line.pending_qty_to_receive > 0)
self._check_valid_request_line(request_line_ids)
self.check_group(request_lines)
for line in request_lines:
items.append([0, 0, self._prepare_item(line)])
return items
class PurchaseRequestLineMakePurchaseOrderItem(models.TransientModel):
_inherit = "purchase.request.line.make.purchase.order.item"
supply_method = fields.Selection(related='line_id.supply_method', string='供货方式')
wiz_id = fields.Many2one(
comodel_name="purchase.request.line.make.purchase.order",
string="Wizard",
required=False,
ondelete="cascade",
readonly=True,
)

View File

@@ -8,7 +8,6 @@
'category': 'sf',
'depends': ['base', 'sf_maintenance', 'jikimo_mini_program'],
'data': [
],
'application': True,

View File

@@ -1,21 +1,30 @@
import json
from odoo import http
from odoo.http import request
from odoo.addons.sf_machine_connect.models.ftp_operate import transfer_nc_files
from odoo.addons.sf_machine_connect.models.ftp_operate import transfer_files
from odoo.addons.sf_base.decorators.api_log import api_log
class MainController(http.Controller):
@http.route('/api/manual_download_program', type='json', methods=['POST'], auth='public', cors='*')
@http.route('/api/manual_download_program', type='json', methods=['POST'], auth='wechat_token', cors='*')
@api_log('人工线下加工编程文件传输', requester='报工系统')
def manual_download_program(self):
"""
人工线下加工传输编程文件
"""
data = json.loads(request.httprequest.data)
maintenance_equipment_name = data.get('maintenance_equipment_name')
maintenance_equipment_id = data.get('maintenance_equipment_id')
model_id = data.get('model_id')
if not maintenance_equipment_name or not model_id:
if not maintenance_equipment_id or not model_id:
return {'code': 400, 'message': '参数错误'}
maintenance_equipment = request.env['maintenance.equipment'].sudo().search([('name', '=', maintenance_equipment_name)], limit=1)
try:
model_id = int(model_id)
except Exception as e:
return {'code': 400, 'message': '参数类型错误'}
maintenance_equipment = request.env['maintenance.equipment'].sudo().search(
[('MTcode', '=', maintenance_equipment_id), ('category_id.equipment_type', '=', '机床')],
limit=1
)
if not maintenance_equipment:
return {'code': 400, 'message': '机台不存在,请扫描正确的机台二维码'}
product = request.env['product.template'].sudo().search([('model_id', '=', model_id)], limit=1)
@@ -45,15 +54,15 @@ class MainController(http.Controller):
}
# 传输nc文件
try:
result = transfer_nc_files(
result = transfer_files(
source_ftp_info,
target_ftp_info,
'/' + str(model_id),
'/',
match_str=r'^\d*_\d*-' + tool_groups_id.name + r'-\w{2}-all\.nc$'
match_str=r'^\d*-' + tool_groups_id.name + r'-\w{2}-all\.nc$'
)
if result:
return {'code': 200, 'message': 'success'}
if len(result) > 0:
return {'code': 200, 'message': '传输成功', 'file_list': result}
else:
return {'code': 404, 'message': '未找到编程文件'}
except Exception as e:

View File

@@ -141,7 +141,7 @@ class QualityCheck(models.Model):
# # 出厂检验报告编号
# report_number = fields.Char('出厂检验报告编号', compute='_compute_report_number', readonly=True)
# 总数量值为调拨单_产品明细_数量
total_qty = fields.Char('总数量', compute='_compute_total_qty')
total_qty = fields.Char('总数量', compute='_compute_total_qty', store=True)
column_nums = fields.Integer('测量值列数', default=1)
@@ -153,9 +153,9 @@ class QualityCheck(models.Model):
for move in record.picking_id.move_ids_without_package:
if move.product_id == record.product_id:
total_qty = int(move.product_uom_qty)
record.total_qty = total_qty if total_qty > 0 else ''
record.total_qty = total_qty if total_qty > 0 else 0
else:
record.total_qty = ''
record.total_qty = 0
# 检验数
check_qty = fields.Integer('检验数', default=lambda self: self._get_default_check_qty())
@@ -735,8 +735,9 @@ class QualityCheck(models.Model):
def _compute_qty_to_test(self):
for qc in self:
if qc.is_lot_tested_fractionally:
rounding = qc.product_id.uom_id.rounding if qc.product_id.uom_id else 0.01
qc.qty_to_test = float_round(qc.qty_line * qc.testing_percentage_within_lot / 100,
precision_rounding=self.product_id.uom_id.rounding, rounding_method="UP")
precision_rounding=rounding, rounding_method="UP")
else:
qc.qty_to_test = qc.qty_line

View File

@@ -23,8 +23,8 @@ class QualityCheckWizard(models.TransientModel):
lot_name = fields.Char(related='current_check_id.lot_name')
lot_line_id = fields.Many2one(related='current_check_id.lot_line_id')
qty_line = fields.Float(related='current_check_id.qty_line')
qty_to_test = fields.Float(related='current_check_id.qty_to_test')
qty_tested = fields.Float(related='current_check_id.qty_tested', readonly=False)
qty_to_test = fields.Float(related='current_check_id.qty_to_test', string='待检')
qty_tested = fields.Float(related='current_check_id.qty_tested', string='已检', readonly=False)
measure = fields.Float(related='current_check_id.measure', readonly=False)
measure_on = fields.Selection(related='current_check_id.measure_on')
quality_state = fields.Selection(related='current_check_id.quality_state')

View File

@@ -1,3 +1,7 @@
pystrich
cpca
pycryptodome==3.20
cpca==0.5.5
wechatpy==1.8.18
pycryptodome==3.22.0
openupgradelib==3.10.0
opcua==0.98.13
openpyxl

View File

@@ -1,3 +1,4 @@
from . import models
from . import commons
from . import controllers
from . import decorators

View File

@@ -25,7 +25,7 @@
'views/menu_fixture_view.xml',
'views/change_base_view.xml',
'views/Printer.xml',
'views/api_log_views.xml',
],
'demo': [
],

View File

@@ -134,7 +134,7 @@ class PrintingUtils(models.AbstractModel):
# 注册中文字体
font_paths = [
"/usr/share/fonts/windows/simsun.ttc", # Windows系统宋体
"/usr/share/fonts/chinese/simsun.ttc", # Windows系统宋体
"c:/windows/fonts/simsun.ttc", # Windows系统宋体另一个位置
"/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", # Linux Droid字体
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", # 文泉驿正黑
@@ -167,10 +167,10 @@ class PrintingUtils(models.AbstractModel):
# 设置字体
if font_found:
c.setFont('SimSun', 14) # 增大字体大小到14pt
c.setFont('SimSun', 10) # 增大字体大小到14pt
else:
# 如果没有找到中文字体,使用默认字体
c.setFont('Helvetica', 14)
c.setFont('Helvetica', 10)
logging.warning("未找到中文字体,将使用默认字体")
# 在右下角绘制二维码,预留边距
@@ -182,7 +182,7 @@ class PrintingUtils(models.AbstractModel):
if buttom_text:
# 在二维码下方绘制文字
text = buttom_text
text_width = c.stringWidth(text, "SimSun" if font_found else "Helvetica", 14) # 准确计算文字宽度
text_width = c.stringWidth(text, "SimSun" if font_found else "Helvetica", 10) # 准确计算文字宽度
text_x = page_width - qr_size - margin + (qr_size - text_width) / 2 # 文字居中对齐
text_y = margin + 20 # 文字位置靠近底部
c.drawString(text_x, text_y, text)

View File

@@ -1,15 +1,18 @@
# -*- coding: utf-8 -*-
import logging
import json
import base64
import logging
from odoo import http
from odoo.http import request
from odoo.addons.sf_base.decorators.api_log import api_log
_logger = logging.getLogger(__name__)
class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/MachineToolGroup', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('机床刀具组', requester='中控系统')
def get_maintenance_tool_groups_Info(self, **kw):
"""
机床刀具组接口

View File

@@ -0,0 +1 @@
from . import api_log

View File

@@ -0,0 +1,62 @@
import functools
import json
import logging
from datetime import datetime
from odoo.http import request
_logger = logging.getLogger(__name__)
def api_log(name=None, requester=None):
"""记录API请求日志的装饰器"""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
start_time = datetime.now()
# 获取请求信息
try:
# 获取请求数据
request_data = json.loads(request.httprequest.data) if request.httprequest.data else {}
# 获取请求路径
path = request.httprequest.path
# 获取请求方法
method = request.httprequest.method
# 获取客户端IP
remote_addr = request.httprequest.remote_addr
# 执行原始函数
result = func(*args, **kwargs)
origin_result = result
if isinstance(result, str):
result = json.loads(result)
# 计算响应时间
end_time = datetime.now()
response_time = (end_time - start_time).total_seconds()
# 创建日志记录
log_vals = {
'name': name or func.__name__,
'path': path,
'method': method,
'request_data': json.dumps(request_data, ensure_ascii=False),
'response_data': json.dumps(result, ensure_ascii=False),
'remote_addr': remote_addr,
'response_time': response_time,
'status': result.get('code') or result.get('ErrorCode') or 500,
'requester': requester,
'responser': '智能工厂'
}
# 异步创建日志记录
request.env['api.request.log'].sudo().with_context(tracking_disable=True).create(log_vals)
return origin_result
except Exception as e:
_logger.error(f"API日志记录失败: {str(e)}")
# 即使日志记录失败,也要返回原始结果
return func(*args, **kwargs)
return wrapper
return decorator

View File

@@ -6,3 +6,4 @@ from . import functional_fixture
from . import tool_other_features
from . import basic_parameters_fixture
from . import ir_sequence
from . import api_log

72
sf_base/models/api_log.py Normal file
View File

@@ -0,0 +1,72 @@
from odoo import models, fields, api
import json, ast
import logging
import requests
_logger = logging.getLogger(__name__)
class ApiRequestLog(models.Model):
_name = 'api.request.log'
_description = '接口请求日志'
_order = 'id desc'
name = fields.Char('接口名称')
path = fields.Char('请求路径')
method = fields.Char('请求方法')
request_data = fields.Text('请求数据')
response_data = fields.Text('响应数据')
remote_addr = fields.Char('客户端IP')
response_time = fields.Float('响应时间(秒)', digits=(16, 6))
status = fields.Integer('状态码')
requester = fields.Char('请求方')
responser = fields.Char('响应方')
@api.model
def log_request(self, method, url, name=None, responser=None, **kwargs):
# Log the request
request_headers = kwargs.get('headers', {})
request_body = kwargs.get('json') or kwargs.get('params') or {}
_logger.info(f"Request: {method} {url} Headers: {request_headers} Body: {request_body}")
# Make the actual request
response = requests.request(method, url, **kwargs)
# Log the response
response_status = response.status_code
response_headers = response.headers
response_body = response.text
response_time = response.elapsed.total_seconds()
_logger.info(f"Response: Status: {response_status} Headers: {response_headers} Body: {response_body}")
try:
# 如果是字符串,先尝试用 ast.literal_eval 安全地转换成 Python 对象
if isinstance(response_body, str):
response_body_obj = json.loads(response_body)
else:
response_body_obj = response_body
# 再使用 json.dumps 转换成标准的 JSON 字符串
response_body = json.dumps(response_body_obj, ensure_ascii=False)
except Exception as e:
_logger.warning(f"转换 response_body 到标准 JSON 失败: {str(e)}")
# 如果转换失败,保持原样
# Save to database
self.sudo().create({
'name': name,
'path': url,
'method': method,
'request_data': request_body,
'response_data': response_body,
'remote_addr': None,
'response_time': response_time,
'status': response_status,
'requester': '智能工厂',
'responser': responser
})
return response

View File

@@ -254,3 +254,6 @@ access_sf_machining_accuracy_admin,sf_machining_accuracy_admin,model_sf_machinin
access_sf_embryo_redundancy,sf_embryo_redundancy,model_sf_embryo_redundancy,base.group_user,1,0,0,0
access_sf_embryo_redundancy_admin,sf_embryo_redundancy_admin,model_sf_embryo_redundancy,base.group_system,1,0,0,0
access_api_request_log_user,api.request.log.user,model_api_request_log,base.group_user,1,0,0,0
access_api_request_log_admin,api.request.log.admin,model_api_request_log,base.group_system,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
254
255
256
257
258
259

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_api_request_log_tree" model="ir.ui.view">
<field name="name">api.request.log.tree</field>
<field name="model">api.request.log</field>
<field name="arch" type="xml">
<tree>
<field name="create_date"/>
<field name="name"/>
<field name="path"/>
<field name="method"/>
<field name="remote_addr"/>
<field name="response_time"/>
<field name="status"/>
</tree>
</field>
</record>
<record id="view_api_request_log_form" model="ir.ui.view">
<field name="name">api.request.log.form</field>
<field name="model">api.request.log</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group>
<field name="name"/>
<field name="path"/>
<field name="method"/>
<field name="remote_addr"/>
</group>
<group>
<field name="response_time"/>
<field name="status"/>
<field name="create_date" string="请求时间"/>
</group>
</group>
<notebook>
<page string="请求数据">
<field name="request_data"/>
</page>
<page string="响应数据">
<field name="response_data"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="action_api_request_log" model="ir.actions.act_window">
<field name="name">API请求日志</field>
<field name="res_model">api.request.log</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_api_request_log"
name="API请求日志"
parent="base.next_id_9"
action="action_api_request_log"
sequence="100"/>
</odoo>

View File

@@ -24,6 +24,8 @@ class SfProductionProcessParameter(models.Model):
obj = super(SfProductionProcessParameter, self).create(vals)
return obj
def create_service_product(self):
if not self.active:
return
service_categ = self.env.ref(
'sf_dlm.product_category_surface_technics_sf').sudo()

View File

@@ -41,7 +41,9 @@
attrs="{'invisible': [('categ_type', 'not in', ['成品','坯料', '原材料'])],'readonly': [('id', '!=', False)]}"/>
<field name="server_product_process_parameters_id" string="工艺参数"
options="{'no_create': True}"
attrs="{'invisible': ['|',('detailed_type', '!=', 'service'),('detailed_type', '=', False)]}"/>
attrs="{'invisible': ['|',('detailed_type', '!=', 'service'),('detailed_type', '=', False)]}"
domain="[('active', '=', 'True'),('outsourced_service_products', '!=', 'True')]"
/>
<field name="cutting_tool_material_id" class="custom_required"
options="{'no_create': True}"
attrs="{'invisible': [('categ_type', '!=', '刀具')],'required': [('categ_type', '=', '刀具')],'readonly': [('id', '!=', False)]}"

View File

@@ -1298,10 +1298,7 @@ class Sf_Dashboard_Connect(http.Controller):
conn = psycopg2.connect(**db_config)
# 获取请求的机床数据
machine_list = ast.literal_eval(kw['machine_list'])
time_threshold = datetime.now() - timedelta(days=1)
alarm_last_24_time = 0.0
alarm_all_time = 0.0
time_threshold = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
def fetch_result_as_dict(cursor):
"""辅助函数:将查询结果转为字典"""
@@ -1311,6 +1308,9 @@ class Sf_Dashboard_Connect(http.Controller):
# 获取当前时间的时间戳
current_timestamp = datetime.now().timestamp()
for item in machine_list:
alarm_last_24_time = 0.0
alarm_all_time = 0.0
euipment_obj = request.env['maintenance.equipment'].sudo().search([('code', '=', item)])
# 机床上线时间段
first_online_duration = current_timestamp - euipment_obj.first_online_time.timestamp()
@@ -1327,8 +1327,8 @@ class Sf_Dashboard_Connect(http.Controller):
cur.execute("""
SELECT * FROM device_data
WHERE device_name = %s
AND device_state in ('待机', '警告', '运行中') AND time >= %s AND process_time IS NOT NULL
ORDER BY time ASC
AND device_state in ('待机', '警告', '运行中') AND time <= %s AND process_time IS NOT NULL
ORDER BY time DESC
LIMIT 1;
""", (item, time_threshold))
last_24_time = fetch_result_as_dict(cur)
@@ -1348,12 +1348,13 @@ class Sf_Dashboard_Connect(http.Controller):
alarm_last_24_nums = []
with conn.cursor() as cur:
cur.execute("""
SELECT DISTINCT ON (alarm_start_time) alarm_time, alarm_start_time
SELECT DISTINCT ON (alarm_start_time, alarm_time) alarm_time, alarm_start_time
FROM device_data
WHERE device_name = %s
AND alarm_start_time IS NOT NULL AND time >= %s;
AND alarm_start_time IS NOT NULL AND alarm_start_time::timestamp >= %s;
""", (item, time_threshold))
results = cur.fetchall()
logging.info("results============:%s" % results)
for result in results:
alarm_last_24_nums.append(result[1])
if result[0]:

View File

@@ -9,9 +9,13 @@ _logger = logging.getLogger(__name__)
class FTP_P(FTP):
"""
重写FTP类重写dirs方法
重写FTP类重写dirs方法,增加编码处理
"""
def __init__(self, host='', user='', passwd='', acct='', timeout=None, encoding='gbk'):
"""初始化时指定编码方式"""
super().__init__(host, user, passwd, acct, timeout)
self.encoding = encoding
def dirs(self, *args):
"""List a directory in long form.
By default list current directory to stdout.
@@ -32,7 +36,50 @@ class FTP_P(FTP):
tempdic['name'] = [file for file in r_files if file != "." and file != ".."]
# 去除. ..
return tempdic
# return [file for file in r_files if file != "." and file != ".."]
def nlst(self, *args):
"""Get a list of files in a directory."""
files = []
def append(line):
try:
if isinstance(line, bytes):
files.append(line.decode(self.encoding))
else:
files.append(line)
except UnicodeDecodeError:
files.append(line.decode('utf-8', errors='replace'))
cmd = 'NLST'
if args:
cmd = cmd + ' ' + args[0]
self.retrlines(cmd, append)
return files
def cwd(self, dirname):
"""Change to a directory."""
try:
if isinstance(dirname, bytes):
dirname = dirname.decode(self.encoding)
return super().cwd(dirname)
except UnicodeEncodeError:
return super().cwd(dirname.encode(self.encoding).decode('utf-8'))
def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
"""Store a file in binary mode."""
try:
if isinstance(cmd, bytes):
cmd = cmd.decode(self.encoding)
return super().storbinary(cmd, fp, blocksize, callback, rest)
except UnicodeEncodeError:
return super().storbinary(cmd.encode(self.encoding).decode('utf-8'), fp, blocksize, callback, rest)
def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
"""Retrieve a file in binary mode."""
try:
if isinstance(cmd, bytes):
cmd = cmd.decode(self.encoding)
return super().retrbinary(cmd, callback, blocksize, rest)
except UnicodeEncodeError:
return super().retrbinary(cmd.encode(self.encoding).decode('utf-8'), callback, blocksize, rest)
# FTP接口类
@@ -133,7 +180,7 @@ class FtpController:
def transfer_nc_files(
def transfer_files(
source_ftp_info,
target_ftp_info,
source_dir,
@@ -151,7 +198,7 @@ def transfer_nc_files(
target_dir: str, 目标FTP上的目标目录
keep_dir: bool, 是否保持目录结构,默认False
"""
trans_status = [False]
transfered_file_list = []
try:
# 连接源FTP
source_ftp = FtpController(
@@ -214,52 +261,94 @@ def transfer_nc_files(
target_path = f"{target_dir}/{relative_path}/{item}"
else:
target_path = f"{target_dir}/{item}"
with open(temp_path, 'rb') as f:
target_ftp.ftp.storbinary(f'STOR {target_path}', f)
# 规范化路径
target_path = target_path.replace('\\', '/').strip('/')
trans_status[0] = True
# 确保目标目录存在
target_dir_path = '/'.join(target_path.split('/')[:-1])
try:
target_ftp.ftp.cwd('/') # 回到根目录
for dir_part in target_dir_path.split('/'):
if dir_part:
try:
target_ftp.ftp.cwd(dir_part)
except:
try:
target_ftp.ftp.mkd(dir_part)
target_ftp.ftp.cwd(dir_part)
except Exception as e:
logging.error(f"创建目录失败 {dir_part}: {str(e)}")
raise
except Exception as e:
logging.error(f"处理目标目录失败: {str(e)}")
raise
# 检查FTP连接状态
try:
target_ftp.ftp.voidcmd('NOOP')
except:
logging.error("FTP连接已断开尝试重新连接")
target_ftp.ftp.connect(target_ftp_info['host'], target_ftp_info['port'])
target_ftp.ftp.login(target_ftp_info['username'], target_ftp_info['password'])
# 上传文件
try:
with open(temp_path, 'rb') as f:
# 检查文件是否可读
content = f.read()
if not content:
raise Exception("临时文件为空")
f.seek(0) # 重置文件指针
target_ftp.ftp.storbinary(f'STOR {target_path}', f)
except Exception as e:
logging.error(f"上传文件失败: {str(e)}")
logging.error(f"目标路径: {target_path}")
raise
transfered_file_list.append(item)
# 删除临时文件
os.remove(temp_path)
logging.info(f"已传输文件: {item}")
# 清空目标目录下的所有内容
try:
target_ftp.ftp.cwd(target_dir)
files = target_ftp.ftp.nlst()
# try:
# target_ftp.ftp.cwd(target_dir)
# files = target_ftp.ftp.nlst()
for f in files:
try:
# 尝试删除文件
target_ftp.ftp.delete(f)
except:
try:
# 如果删除失败,可能是目录,递归删除目录
def remove_dir(path):
target_ftp.ftp.cwd(path)
sub_files = target_ftp.ftp.nlst()
for sf in sub_files:
try:
target_ftp.ftp.delete(sf)
except:
remove_dir(f"{path}/{sf}")
target_ftp.ftp.cwd('..')
target_ftp.ftp.rmd(path)
# for f in files:
# try:
# # 尝试删除文件
# target_ftp.ftp.delete(f)
# except:
# try:
# # 如果删除失败,可能是目录,递归删除目录
# def remove_dir(path):
# target_ftp.ftp.cwd(path)
# sub_files = target_ftp.ftp.nlst()
# for sf in sub_files:
# try:
# target_ftp.ftp.delete(sf)
# except:
# remove_dir(f"{path}/{sf}")
# target_ftp.ftp.cwd('..')
# target_ftp.ftp.rmd(path)
remove_dir(f"{target_dir}/{f}")
except:
logging.error(f"无法删除 {f}")
pass
# remove_dir(f"{target_dir}/{f}")
# except:
# logging.error(f"无法删除 {f}")
# pass
logging.info(f"已清空目标目录 {target_dir}")
except Exception as e:
logging.error(f"清空目标目录失败: {str(e)}")
raise Exception(f"清空目标目录失败: {str(e)}")
# logging.info(f"已清空目标目录 {target_dir}")
# except Exception as e:
# logging.error(f"清空目标目录失败: {str(e)}")
# raise Exception(f"清空目标目录失败: {str(e)}")
# 开始遍历
traverse_dir(source_dir)
logging.info("所有文件传输完成")
return trans_status[0]
return transfered_file_list
except Exception as e:
logging.error(f"传输过程出错: {str(e)}")

View File

@@ -2,6 +2,8 @@
import json
import base64
import logging
import qrcode
from io import BytesIO
from datetime import timedelta
import requests
from odoo.addons.sf_base.commons.common import Common
@@ -34,8 +36,7 @@ class SfMaintenanceEquipmentAGVLog(models.Model):
class SfMaintenanceEquipment(models.Model):
_name = 'maintenance.equipment'
_inherit = ['maintenance.equipment', 'printing.utils']
_inherit = 'maintenance.equipment'
_description = '设备'
crea_url = "/api/machine_tool/create"
@@ -844,7 +845,7 @@ class SfMaintenanceEquipment(models.Model):
box_size=10,
border=4,
)
qr.add_data(record.id)
qr.add_data(record.MTcode)
qr.make(fit=True)
qr_image = qr.make_image(fill_color="black", back_color="white")
@@ -855,66 +856,6 @@ class SfMaintenanceEquipment(models.Model):
record.qr_code_image = encoded_image
def print_single_method(self):
print('self.name========== %s' % self.name)
self.ensure_one()
qr_code_data = self.qr_code_image
if not qr_code_data:
raise UserError("没有找到二维码数据。")
maintenance_equipment_name = self.name
# host = "192.168.50.110" # 可以根据实际情况修改
# port = 9100 # 可以根据实际情况修改
# 获取默认打印机配置
printer_config = self.env['printer.configuration'].sudo().search([('model', '=', self._name)], limit=1)
if not printer_config:
raise UserError('请先配置打印机')
host = printer_config.printer_id.ip_address
port = printer_config.printer_id.port
self.print_qr_code(maintenance_equipment_name, host, port)
def generate_zpl_code(self, code):
"""生成ZPL代码用于打印二维码标签
Args:
code: 需要编码的内容
Returns:
str: ZPL指令字符串
"""
zpl_code = "^XA\n" # 开始ZPL格式
# 设置打印参数
zpl_code += "^LH0,0\n" # 设置标签起始位置
zpl_code += "^CI28\n" # 设置中文编码
zpl_code += "^PW400\n" # 设置打印宽度为400点
zpl_code += "^LL300\n" # 设置标签长度为300点
# 打印标题
zpl_code += "^FO10,20\n" # 设置标题位置
zpl_code += "^A0N,30,30\n" # 设置字体大小
zpl_code += "^FD机床二维码^FS\n" # 打印标题文本
# 打印二维码
zpl_code += "^FO50,60\n" # 设置二维码位置
zpl_code += f"^BQN,2,8\n" # 设置二维码参数:模式2,放大倍数8
zpl_code += f"^FDLA,{code}^FS\n" # 二维码内容
# 打印编码文本
zpl_code += "^FO50,220\n" # 设置编码文本位置
zpl_code += "^A0N,25,25\n" # 设置字体大小
zpl_code += f"^FD编码: {code}^FS\n" # 打印编码文本
# 打印日期
zpl_code += "^FO50,260\n"
zpl_code += "^A0N,20,20\n"
zpl_code += f"^FD打印日期: {fields.Date.today()}^FS\n"
zpl_code += "^PQ1\n" # 打印1份
zpl_code += "^XZ\n" # 结束ZPL格式
return zpl_code
class SfRobotAxisNum(models.Model):
_name = 'sf.robot.axis.num'

View File

@@ -1055,11 +1055,6 @@
<xpath expr="//group/field[@name='location']" position="after">
<field name="qr_code_image" widget="image" readonly="1" attrs="{'invisible': [('equipment_type', '!=', '机床')]}" />
<label for="print_single_method"/>
<div class="col-12 col-lg-6 o_setting_box" style="white-space: nowrap">
<button type="object" class="oe_highlight" name='print_single_method' string="打印机床二维码"
attrs="{'invisible': [('equipment_type', '!=', '机床')]}"/>
</div>
</xpath>
<xpath expr="//page[@name='maintenance']" position="after">
<page name="network_config" string="网络配置" attrs="{'invisible': [('equipment_type', '!=', '机床')]}" >

View File

@@ -48,6 +48,7 @@
'views/mrp_workorder_batch_replan.xml',
'views/purchase_order_view.xml',
'views/product_template_views.xml',
'views/stock_warehouse_orderpoint.xml',
],
'assets': {

View File

@@ -6,12 +6,14 @@ from datetime import datetime
from odoo.addons.sf_manufacturing.models.agv_scheduling import RepeatTaskException
from odoo import http
from odoo.http import request
from odoo.addons.sf_base.decorators.api_log import api_log
class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/GetWoInfo', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('获取工单', requester='中控系统')
def get_Work_Info(self, **kw):
"""
自动化传递工单号获取工单信息
@@ -107,6 +109,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/QcCheck', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('工件预调(前置三元检测)', requester='中控系统')
def get_qcCheck(self, **kw):
"""
工件预调(前置三元检测)
@@ -149,6 +152,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/FeedBackStart', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('工单开始', requester='中控系统')
def button_Work_START(self, **kw):
"""
工单任务开始
@@ -198,6 +202,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/FeedBackEnd', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('工单结束', requester='中控系统')
def button_Work_End(self, **kw):
"""
工单任务结束
@@ -249,6 +254,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/PartQualityInspect', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('零件检测(后置三元检测)', requester='中控系统')
def PartQualityInspect(self, **kw):
"""
零件质检
@@ -295,6 +301,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/CMMProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('CMM测量程序下载', requester='中控系统')
def CMMProgDolod(self, **kw):
"""
中控系统传递RFID编号给MES获取测量程序文件。Ftp下载文件
@@ -335,6 +342,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/NCProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('CAM加工程序下载', requester='中控系统')
def NCProgDolod(self, **kw):
"""
中控系统传递RFID编号给MES获取程序单及程序文件。Ftp下载文件
@@ -376,6 +384,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/LocationChange', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('库位变更', requester='中控系统')
def LocationChange(self, **kw):
"""
库位变更
@@ -480,6 +489,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('AGV运送上产线', requester='中控系统')
def AGVToProduct(self, **kw):
"""
AGV运送上产线完成
@@ -552,6 +562,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('AGV运送下产线', requester='中控系统')
def AGVDownProduct(self, **kw):
"""
MES调度AGV搬运零件AGV托盘到产线接驳站。

View File

@@ -18,4 +18,4 @@ from . import quick_easy_order
from . import purchase_order
from . import quality_check
from . import purchase_request_line
from . import workorder_printer
from . import stock_warehouse_orderpoint

View File

@@ -928,12 +928,15 @@ class MrpProduction(models.Model):
'sf_stock.stock_route_process_outsourcing').id)]
for product_id, request_line_list in grouped_purchase_request_line_sorted_list.items():
cur_request_line = request_line_list[0]
cur_request_line['product_qty'] = len(request_line_list)
if cur_request_line['product_qty'] == 1:
cur_request_line['product_qty'] = len(request_line_list)
# cur_request_line['product_qty'] = cur_request_line['product_qty']
cur_request_line['request_id'] = pr.id
cur_request_line['origin'] = ", ".join({item['production_name'] for item in request_line_list if item.get('production_name')})
cur_request_line.pop('group_id', None)
cur_request_line.pop('production_name', None)
self.env["purchase.request.line"].create(cur_request_line)
pr.button_approved()
# 外协出入库单处理
def get_subcontract_pick_purchase(self):
@@ -961,7 +964,7 @@ class MrpProduction(models.Model):
if not sorted_workorders:
return
for workorders in reversed(sorted_workorders):
self.env['stock.picking'].create_outcontract_picking(workorders, production, sorted_workorders)
# self.env['stock.picking'].create_outcontract_picking(workorders, production, sorted_workorders)
# self.env['purchase.order'].get_purchase_order(workorders, production, product_id_to_production_names)
purchase_request_line = purchase_request_line + self.env['purchase.order'].get_purchase_request(
workorders, production)
@@ -1821,7 +1824,7 @@ class MrpProduction(models.Model):
logging.info('update_programming_state error:%s' % e)
raise UserError("更新编程单状态失败,请联系管理员")
model_id = fields.Char('模型id', related='product_id.model_id')
model_id = fields.Char('模型ID', related='product_id.model_id')
# 编程记录

View File

@@ -85,6 +85,7 @@ class ResMrpWorkOrder(models.Model):
item.pr_mp_count = len(pr_ids)
else:
item.pr_mp_count = 0
@api.depends('state')
def _compute_back_button_display(self):
for record in self:
@@ -442,21 +443,11 @@ class ResMrpWorkOrder(models.Model):
@api.depends('state', 'production_id.name')
def _compute_surface_technics_purchase_ids(self):
for order in self:
if order.routing_type == '表面工艺' and order.state not in ['cancel']:
# domain = [('group_id', '=', self.production_id.procurement_group_id.id),
# ('purchase_type', '=', 'consignment'), ('state', '!=', 'cancel')]
domain = [('purchase_type', '=', 'consignment'),
('origin', 'like', '%' + self.production_id.name + '%'),
('state', '!=', 'cancel')]
purchase = self.env['purchase.order'].search(domain)
order.surface_technics_purchase_count = 0
if not purchase:
order.surface_technics_purchase_count = 0
for po in purchase:
if any(
line.product_id and line.product_id.server_product_process_parameters_id == order.surface_technics_parameters_id
for line in po.order_line):
order.surface_technics_purchase_count = 1
pr_ids = self.env['purchase.request'].sudo().search(
[('origin', 'like', self.production_id.name), ('is_subcontract', '=', 'True'),
('state', '!=', 'rejected')])
if pr_ids.purchase_count:
order.surface_technics_purchase_count = pr_ids.purchase_count
else:
order.surface_technics_purchase_count = 0
@@ -484,6 +475,7 @@ class ResMrpWorkOrder(models.Model):
'view_mode': 'tree,form',
})
return action
def action_view_surface_technics_purchase(self):
self.ensure_one()
# if self.routing_type == '表面工艺':
@@ -500,22 +492,26 @@ class ResMrpWorkOrder(models.Model):
# if technology_design.is_auto is False:
# domain = [('origin', '=', self.production_id.name)]
# else:
purchase_orders_id = self._get_surface_technics_purchase_ids()
result = {
"type": "ir.actions.act_window",
"res_model": "purchase.order",
"res_id": purchase_orders_id.id,
# "domain": [['id', 'in', self.purchase_id]],
"name": _("Purchase Orders"),
'view_mode': 'form',
}
return result
pr_ids = self.env['purchase.request'].sudo().search(
[('origin', 'like', self.production_id.name), ('is_subcontract', '=', 'True'),
('state', '!=', 'rejected')])
# purchase_orders_id = self._get_surface_technics_purchase_ids()
# result = {
# "type": "ir.actions.act_window",
# "res_model": "purchase.order",
# "res_id": purchase_orders_id.id,
# # "domain": [['id', 'in', self.purchase_id]],
# "name": _("Purchase Orders"),
# 'view_mode': 'form',
# }
return pr_ids.action_view_purchase_order()
def _get_surface_technics_purchase_ids(self):
domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment')]
domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment'),
('state', '!=', 'cancel')]
# domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment')]
# domain = [('group_id', '=', self.production_id.procurement_group_id.id), ('purchase_type', '=', 'consignment')]
purchase_orders = self.env['purchase.order'].search(domain, order='id desc', # 按创建时间降序(最新的在前)
limit=1)
purchase_orders = self.env['purchase.order'].search(domain, order='id desc')
purchase_orders_id = self.env['purchase.order']
for po in purchase_orders:
for line in po.order_line:
@@ -1242,8 +1238,16 @@ class ResMrpWorkOrder(models.Model):
}]
return workorders_values_str
def check_lot_exists(self, picking_id, lot_id):
return bool(
picking_id.move_ids.move_line_ids.filtered(
lambda line: line.lot_id.id == lot_id
)
)
def _process_compute_state(self):
for workorder in self:
sorted_workorders = sorted(self, key=lambda x: x.sequence)
for workorder in sorted_workorders:
# 如果工单的工序没有进行排序则跳出循环
if workorder.production_id.workorder_ids.filtered(lambda wk: wk.sequence == 0):
continue
@@ -1262,20 +1266,37 @@ class ResMrpWorkOrder(models.Model):
workorder.state = 'pending'
continue
# ================= 如果制造订单制造类型为【人工线下加工】==========================
# lot_id = workorder.production_id.move_raw_ids.move_line_ids.lot_id
# picking_ids = workorder.production_id.picking_ids.filtered(
# lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前')
# exists = any(
# move_line.lot_id == lot_id
# for picking in picking_ids
# for move in picking.move_ids
# for move_line in move.move_line_ids
# )
if (workorder.production_id.production_type == '人工线下加工'
and workorder.production_id.schedule_state == ''
and len(workorder.production_id.picking_ids.filtered(
lambda w: w.state not in ['done', 'cancel'])) == 0):
and workorder.production_id.programming_state == '编程'):
# and workorder.production_id.programming_state == '已编程'
if workorder.is_subcontract is True:
if workorder.production_id.state == 'rework':
workorder.state = 'waiting'
continue
purchase_orders_id = self._get_surface_technics_purchase_ids()
if purchase_orders_id.state == 'purchase':
purchase_count = 0
for purchase_order in purchase_orders_id:
for purchase_order_line in purchase_order.order_line:
if purchase_order_line.product_id.server_product_process_parameters_id.id == workorder.surface_technics_parameters_id.id:
purchase_count = purchase_order_line.product_qty
if purchase_orders_id.state == 'purchase' and purchase_count>=workorder.production_id.product_qty:
workorder.state = 'ready'
move_out = workorder.move_subcontract_workorder_ids[1]
picking_id = workorder.production_id.picking_ids.filtered(
lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
move_out = picking_id.move_ids
# move_out = workorder.move_subcontract_workorder_ids[1]
for mo in move_out:
if workorder.production_id.bom_id.bom_line_ids.product_id.id != mo.product_id.id:
continue
if mo.state != 'done':
mo.write({'state': 'assigned', 'production_id': False})
if not mo.move_line_ids:
@@ -1291,6 +1312,7 @@ class ResMrpWorkOrder(models.Model):
else:
workorder.state = 'ready'
continue
continue
# ================= 如果制造订单刀具状态为[无效刀、缺刀] 或者 制造订单状态为[返工]==========================
if (workorder.production_id.tool_state in ['1', '2'] or workorder.production_id.state == 'rework'
or workorder.production_id.schedule_state != '已排'
@@ -1302,26 +1324,22 @@ class ResMrpWorkOrder(models.Model):
workorder.state = 'ready'
elif workorder.state != 'waiting':
workorder.state = 'waiting'
# =========== 特殊工艺工单处理 ===================
# if workorder.routing_type == '表面工艺' and workorder.is_subcontrac:
# purchase_order = self.env['purchase.order'].search(
# [('origin', 'ilike', workorder.production_id.name)])
# if purchase_order.picking_ids.filtered(lambda p: p.state in ['waiting', 'confirmed', 'assigned']):
# workorder.state = 'waiting'
# continue
if workorder.technology_design_id.routing_tag == 'special':
if workorder.is_subcontract is False:
workorder.state = 'ready'
else:
if len(workorder.production_id.picking_ids.filtered(
lambda w: w.state not in ['done',
'cancel'])) == 0 and workorder.production_id.programming_state == '已编程':
if workorder.production_id.programming_state == '已编程':
purchase_orders_id = self._get_surface_technics_purchase_ids()
if purchase_orders_id:
if purchase_orders_id.state == 'purchase':
workorder.state = 'ready'
move_out = workorder.move_subcontract_workorder_ids[1]
picking_id = workorder.production_id.picking_ids.filtered(
lambda
wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
move_out = picking_id.move_ids
for mo in move_out:
if workorder.production_id.bom_id.bom_line_ids.product_id.id != mo.product_id.id:
continue
if mo.state != 'done':
mo.write({'state': 'assigned', 'production_id': False})
if not mo.move_line_ids:
@@ -1361,16 +1379,25 @@ class ResMrpWorkOrder(models.Model):
# 判断是否有坯料的序列号信息
boolean = False
if self.production_id.move_raw_ids:
if self.production_id.move_raw_ids[0].product_id.categ_type == '坯料':
if self.production_id.move_raw_ids[0].product_id.categ_type == '坯料' and \
self.production_id.move_raw_ids[0].product_id.tracking == 'serial':
if self.production_id.move_raw_ids[0].move_line_ids:
if self.production_id.move_raw_ids[0].move_line_ids:
if self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name:
boolean = True
if self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name:
boolean = True
else:
boolean = True
if not boolean:
raise UserError('制造订单【%s】缺少组件的序列号信息!' % self.production_id.name)
self.pro_code = self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name
self.pro_code = False # 默认值
if (
self.production_id
and self.production_id.move_raw_ids
and len(self.production_id.move_raw_ids) > 0
and self.production_id.move_raw_ids[0].move_line_ids
and len(self.production_id.move_raw_ids[0].move_line_ids) > 0
and self.production_id.move_raw_ids[0].move_line_ids[0].lot_id
):
self.pro_code = self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name
# cnc校验
if self.production_id.production_type == '自动化产线加工':
cnc_workorder = self.search(
@@ -1394,7 +1421,10 @@ class ResMrpWorkOrder(models.Model):
# 表面工艺外协出库单
if self.routing_type == '表面工艺':
if self.is_subcontract is True:
move_out = self.move_subcontract_workorder_ids[1]
picking_id = self.production_id.picking_ids.filtered(
lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
move_out = picking_id.move_ids
# move_out = self.move_subcontract_workorder_ids[1]
# move_out = self.env['stock.move'].search(
# [('location_id', '=', self.env['stock.location'].search(
# [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id),
@@ -1402,6 +1432,8 @@ class ResMrpWorkOrder(models.Model):
# [('barcode', 'ilike', 'VL-SPOC')]).id),
# ('origin', '=', self.production_id.name), ('state', 'not in', ['cancel', 'done'])])
for mo in move_out:
if self.production_id.bom_id.bom_line_ids.product_id.id != mo.product_id.id:
continue
if mo.state != 'done':
mo.write({'state': 'assigned', 'production_id': False})
if not mo.move_line_ids:
@@ -1534,7 +1566,8 @@ class ResMrpWorkOrder(models.Model):
lambda wo: wo.is_subcontract == True and wo.state != 'cancel').sorted('sequence')
if self == subcontract_workorders[-1]:
# 给下一个库存移动就绪
self.move_subcontract_workorder_ids[0].move_dest_ids._action_done()
if self.move_subcontract_workorder_ids:
self.move_subcontract_workorder_ids[0].move_dest_ids._action_done()
# self.production_id.button_mark_done()
tem_date_planned_finished = record.date_planned_finished
tem_date_finished = record.date_finished
@@ -1813,8 +1846,8 @@ class ResMrpWorkOrder(models.Model):
orderby=orderby,
lazy=lazy
)
model_id = fields.Char('模型id', related='production_id.model_id')
model_id = fields.Char('模型ID', related='production_id.model_id')
class CNCprocessing(models.Model):

View File

@@ -787,7 +787,7 @@ class ResProductMo(models.Model):
glb_url = fields.Char('glb文件地址')
area = fields.Float('表面积(m²)')
auto_machining = fields.Boolean('自动化加工(模型识别)', default=False)
model_id = fields.Char('模型id')
model_id = fields.Char('模型ID')
@api.depends('name')
@@ -1030,6 +1030,7 @@ class ResProductMo(models.Model):
'single_manufacturing': product_id.single_manufacturing,
'is_bfm': True,
'active': True,
'tracking': finish_product.tracking, # 坯料的跟踪方式跟随成品
}
# 外协和采购生成的坯料需要根据材料型号绑定供应商
if route_type == 'subcontract' or route_type == 'purchase':

View File

@@ -59,6 +59,88 @@ class PurchaseOrder(models.Model):
production_id = self.env['mrp.production'].search([('origin', 'in', origins)])
purchase.production_count = len(production_id)
def process_replenish(self,production,total_qty):
record = self
bom_line_id = production.bom_id.bom_line_ids
replenish = self.env['stock.warehouse.orderpoint'].search([
('product_id', '=', bom_line_id.product_id.id),
(
'location_id', '=', self.env.ref('sf_stock.stock_location_outsourcing_material_receiving_area').id),
# ('state', 'in', ['draft', 'confirmed'])
], limit=1)
if not replenish:
replenish_model = self.env['stock.warehouse.orderpoint']
replenish = replenish_model.create({
'product_id': bom_line_id.product_id.id,
'location_id': self.env.ref(
'sf_stock.stock_location_outsourcing_material_receiving_area').id,
'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
'group_id': record.group_id.id,
'qty_to_order': total_qty,
'origin': record.name,
})
else:
replenish.write({
'product_id': bom_line_id.product_id.id,
'location_id': self.env.ref(
'sf_stock.stock_location_outsourcing_material_receiving_area').id,
'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
'group_id': record.group_id.id,
'qty_to_order': total_qty + replenish.qty_to_order,
'origin': record.name + ',' + replenish.origin,
})
replenish.action_replenish()
def outsourcing_service_replenishment(self):
record = self
if record.purchase_type != 'consignment':
return
grouped_lines = {}
for line in record.order_line:
if line.related_product.id not in grouped_lines:
grouped_lines[line.related_product.id] = []
grouped_lines[line.related_product.id].append(line)
for product_id,lines in grouped_lines.items():
production = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1)
if not production:
continue
total_qty = sum(line.product_qty for line in lines)
record.process_replenish(production,total_qty)
for product_id,lines in grouped_lines.items():
productions = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1)
if not productions:
continue
# production.bom_id.bom_line_ids.product_id
location_id = self.env['stock.location'].search([('name', '=', '制造前')])
quants = self.env['stock.quant'].search([
('product_id', '=', productions.bom_id.bom_line_ids.product_id.id),
('location_id', '=', location_id.id)
])
total_qty = sum(quants.mapped('quantity')) # 计算该位置的总库存量
is_available = total_qty > 0
if not is_available:
raise UserError('请先完成坯料入库')
for production_id in productions:
work_ids = production_id.workorder_ids.filtered(
lambda wk: wk.state not in ['done', 'rework', 'cancel'])
if not work_ids:
continue
min_sequence_wk = min(work_ids, key=lambda wk: wk.sequence)
if min_sequence_wk.is_subcontract and min_sequence_wk.state == 'ready':
picking_id = production_id.picking_ids.filtered(
lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
move_out = picking_id.move_ids
for mo in move_out:
if production_id.bom_id.bom_line_ids.product_id.id != mo.product_id.id:
continue
if mo.state != 'done':
mo.write({'state': 'assigned', 'production_id': False})
if not mo.move_line_ids:
self.env['stock.move.line'].create(
mo.get_move_line(production_id, min_sequence_wk))
# product = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1)
# match = re.search(r'(S\d{5}-\d)',product.name)
# pass
def button_confirm(self):
for record in self:
for line in record.order_line:
@@ -66,38 +148,17 @@ class PurchaseOrder(models.Model):
raise UserError('请对【产品】中的【数量】进行输入')
if line.price_unit <= 0:
raise UserError('请对【产品】中的【单价】进行输入')
if record.purchase_type == 'consignment':
bom_line_id = record.order_line[0].purchase_request_lines.request_id.bom_id.bom_line_ids
replenish = self.env['stock.warehouse.orderpoint'].search([
('product_id', '=', bom_line_id.product_id.id),
(
'location_id', '=', self.env.ref('sf_stock.stock_location_outsourcing_material_receiving_area').id),
# ('state', 'in', ['draft', 'confirmed'])
], limit=1)
if not replenish:
replenish_model = self.env['stock.warehouse.orderpoint']
replenish = replenish_model.create({
'product_id': bom_line_id.product_id.id,
'location_id': self.env.ref(
'sf_stock.stock_location_outsourcing_material_receiving_area').id,
'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
'group_id': record.group_id.id,
'qty_to_order': 1,
'origin': record.name,
})
else:
replenish.write({
'product_id': bom_line_id.product_id.id,
'location_id': self.env.ref(
'sf_stock.stock_location_outsourcing_material_receiving_area').id,
'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
'group_id': record.group_id.id,
'qty_to_order': 1 + replenish.qty_to_order,
'origin': record.name + ',' + replenish.origin,
})
replenish.action_replenish()
record.outsourcing_service_replenishment()
res = super(PurchaseOrder, self).button_confirm()
return super(PurchaseOrder, self).button_confirm()
for line in self.order_line:
# 将产品不追踪序列号的行项目设置qty_done
if not line.move_ids:
continue
if line.move_ids and line.move_ids[0].product_id.tracking == 'none':
line.move_ids[0].quantity_done = line.move_ids[0].product_qty
return res
origin_sale_id = fields.Many2one('sale.order', string='销售订单号', store=True, compute='_compute_origin_sale_id')
@@ -167,6 +228,24 @@ class PurchaseOrderLine(models.Model):
)
record.part_number = filtered_order_line.product_id.part_number
record.part_name = filtered_order_line.product_id.part_name
elif record.order_id.purchase_type == 'consignment':
product_name = ''
match = re.search(r'(S\d{5}-\d)', record.related_product.name)
# 如果匹配成功,提取结果
if match:
product_name = match.group(0)
sale_order_name = ''
match_sale = re.search(r'S(\d+)', record.related_product.name)
if match_sale:
sale_order_name = match_sale.group(0)
sale_order = self.env['sale.order'].sudo().search(
[('name', '=', sale_order_name)])
if sale_order:
filtered_order_line = sale_order.order_line.filtered(
lambda order_line: re.search(f'{product_name}$', order_line.product_id.name)
)
record.part_number = filtered_order_line.product_id.part_number
record.part_name = filtered_order_line.product_id.part_name
else:
record.part_number = record.product_id.part_number
record.part_name = record.product_id.part_name

View File

@@ -59,6 +59,7 @@ class SaleOrder(models.Model):
line.product_id.product_tmpl_id.copy_template(product_template_id)
# 将模板上的single_manufacturing属性复制到成品上
line.product_id.single_manufacturing = product_template_id.single_manufacturing
line.product_id.tracking = product_template_id.tracking
order_id = self
product = line.product_id

View File

@@ -2,7 +2,7 @@
import logging
from odoo import fields, models, api
from odoo.exceptions import UserError, ValidationError
from odoo.tools import str2bool
# from odoo.tools import str2bool
class SfProductionProcessParameter(models.Model):
@@ -20,13 +20,6 @@ class SfProductionProcessParameter(models.Model):
is_product_button = fields.Boolean(compute='_compute_is_product_button',default=False)
is_delete_button = fields.Boolean(compute='_compute_is_delete_button', default=False)
routing_id = fields.Many2one('mrp.routing.workcenter', string="工序")
@api.depends('outsourced_service_products')
def _compute_service_products(self):
for record in self:
# 假设取第一条作为主明细
record.service_products = record.outsourced_service_products.id if record.outsourced_service_products else False
def _inverse_service_products(self):
for record in self:
if record.service_products:
@@ -34,6 +27,12 @@ class SfProductionProcessParameter(models.Model):
record.outsourced_service_products = record.service_products.ids if record.service_products else False
else:
record.outsourced_service_products = False
@api.depends('outsourced_service_products')
def _compute_service_products(self):
for record in self:
# 假设取第一条作为主明细
record.service_products = record.outsourced_service_products[0].id if record.outsourced_service_products else False
def name_get(self):
result = []
for record in self:

View File

@@ -631,28 +631,62 @@ class StockPicking(models.Model):
move.action_clear_lines_show_details()
move.action_show_details()
res = super().button_validate()
picking_type_in = self.env.ref('sf_manufacturing.outcontract_picking_in').id
if res is True and self.picking_type_id.id == picking_type_in:
lot_ids = None
product_ids = self.move_ids.mapped('product_id')
if not self.move_ids[0].product_id.single_manufacturing and self.move_ids[0].product_id.tracking == 'none':
lot_ids = self.move_ids.move_line_ids.mapped('lot_id')
production_ids = self.sale_order_id.mrp_production_ids if self.sale_order_id else self.env['mrp.production']
if res and self.location_id.name == '外协收料区' and self.location_dest_id.name == '制造前':
# 如果是最后一张外协入库单,则设置库存位置的预留数量
move_in = self.move_ids
if move_in:
workorder = move_in.subcontract_workorder_id
workorders = workorder.production_id.workorder_ids
subcontract_workorders = workorders.filtered(
lambda wo: wo.is_subcontract == True and wo.state != 'cancel').sorted('sequence')
# if workorder == subcontract_workorders[-1]:
# self.env['stock.quant']._update_reserved_quantity(
# move_in.product_id, move_in.location_dest_id, move_in.product_uom_qty,
# lot_id=move_in.move_line_ids.lot_id,
# package_id=False, owner_id=False, strict=False
# )
workorder.button_finish()
picking_type_out = self.env.ref('sf_manufacturing.outcontract_picking_out').id
if res and self.picking_type_id.id == picking_type_out:
move_out = self.move_ids
if move_out:
workorder = move_out.subcontract_workorder_id
workorder.button_start()
for production_id in production_ids:
if lot_ids:
lot_id = production_id.move_raw_ids.move_line_ids.lot_id
# picking_ids = production_id.picking_ids.filtered(
# lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前')
if lot_id in lot_ids:
workorder_id = production_id.workorder_ids.filtered(
lambda a: a.state == 'progress' and a.is_subcontract)
if not workorder_id:
continue
workorder_id.button_finish()
else:
workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'progress' and a.is_subcontract)
if not workorder_id:
continue
workorder_id.button_finish()
# lot_id = workorder.production_id.move_raw_ids.move_line_ids.lot_id
# picking_ids = workorder.production_id.picking_ids.filtered(
# lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前')
# if move_in:
# workorder = move_in.subcontract_workorder_id
# workorders = workorder.production_id.workorder_ids
# subcontract_workorders = workorders.filtered(
# lambda wo: wo.is_subcontract == True and wo.state != 'cancel').sorted('sequence')
# # if workorder == subcontract_workorders[-1]:
# # self.env['stock.quant']._update_reserved_quantity(
# # move_in.product_id, move_in.location_dest_id, move_in.product_uom_qty,
# # lot_id=move_in.move_line_ids.lot_id,
# # package_id=False, owner_id=False, strict=False
# # )
# workorder.button_finish()
if res and self.location_id.name == '制造前' and self.location_dest_id.name == '外协加工区':
for production_id in production_ids:
if lot_ids:
lot_id = production_id.move_raw_ids.move_line_ids.lot_id
# picking_ids = production_id.picking_ids.filtered(
# lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前')
if lot_id in lot_ids:
workorder_id = production_id.workorder_ids.filtered(
lambda a: a.state == 'progress' and a.is_subcontract)
if not workorder_id:
continue
workorder_id.button_finish()
else:
workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'ready' and a.is_subcontract)
if not workorder_id:
continue
workorder_id.button_start()
if self.location_id.name == '成品存货区' and self.location_dest_id.name == '客户':
sale_id = self.env['sale.order'].sudo().search(
[('name', '=', self.origin)])
@@ -675,6 +709,7 @@ class StockPicking(models.Model):
# 创建 外协出库入单
def create_outcontract_picking(self, workorders, item, sorted_workorders):
production = workorders[0].production_id
for workorder in workorders:
if workorder.move_subcontract_workorder_ids:
workorder.move_subcontract_workorder_ids.write({'state': 'cancel'})
@@ -706,7 +741,7 @@ class StockPicking(models.Model):
})
moves_in = self.env['stock.move'].sudo().with_context(context).create(
self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_in,
procurement_group_id.id, move_dest_id))
procurement_group_id.id, move_dest_id, production.product_uom_qty))
picking_in = self.create(
moves_in._get_new_picking_values_Res(item, workorder, 'WH/OCIN/'))
# pick_ids.append(picking_in.id)
@@ -716,7 +751,7 @@ class StockPicking(models.Model):
# self.env.context.get('default_production_id')
moves_out = self.env['stock.move'].sudo().with_context(context).create(
self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_out,
procurement_group_id.id, moves_in.id))
procurement_group_id.id, moves_in.id, production.product_uom_qty))
workorder.write({'move_subcontract_workorder_ids': [(6, 0, [moves_in.id, moves_out.id])]})
picking_out = self.create(
moves_out._get_new_picking_values_Res(item, workorder, 'WH/OCOUT/'))
@@ -848,7 +883,7 @@ class ReStockMove(models.Model):
traceback_error = traceback.format_exc()
logging.error("零件图号 零件名称获取失败:%s" % traceback_error)
def _get_stock_move_values_Res(self, item, picking_type_id, group_id, move_dest_ids=False):
def _get_stock_move_values_Res(self, item, picking_type_id, group_id, move_dest_ids=False, product_uom_qty=1.0):
route_id = self.env.ref('sf_manufacturing.route_surface_technology_outsourcing').id
stock_rule = self.env['stock.rule'].sudo().search(
[('route_id', '=', route_id), ('picking_type_id', '=', picking_type_id)])
@@ -857,7 +892,7 @@ class ReStockMove(models.Model):
'company_id': item.company_id.id,
'product_id': item.bom_id.bom_line_ids.product_id.id,
'product_uom': item.bom_id.bom_line_ids.product_uom_id.id,
'product_uom_qty': 1.0,
'product_uom_qty': product_uom_qty,
'location_id': stock_rule.location_src_id.id,
'location_dest_id': stock_rule.location_dest_id.id,
'origin': item.name,
@@ -895,6 +930,8 @@ class ReStockMove(models.Model):
}
def get_move_line(self, production_id, sorted_workorders):
# if not self.move_ids[0].product_id.single_manufacturing and self.move_ids[0].product_id.tracking == 'none':
qty = production_id.product_qty
return {
'move_id': self.id,
'product_id': self.product_id.id,
@@ -902,7 +939,7 @@ class ReStockMove(models.Model):
'location_id': self.picking_id.location_id.id,
'location_dest_id': self.picking_id.location_dest_id.id,
'picking_id': self.picking_id.id,
'reserved_uom_qty': 1.0,
'reserved_uom_qty': qty,
'lot_id': production_id.move_line_raw_ids.lot_id.id,
'company_id': self.env.company.id,
# 'workorder_id': '' if not sorted_workorders else sorted_workorders.id,

View File

@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
# Part of SmartGo. See LICENSE file for full copyright and licensing details.
import base64
from io import BytesIO
from odoo import api, fields, models, SUPERUSER_ID, _
class StockWarehouseOrderpoint(models.Model):
_inherit = 'stock.warehouse.orderpoint'
origin = fields.Char(string='来源')
_order = 'create_date DESC'

View File

@@ -110,12 +110,12 @@
</xpath>
<xpath expr="//sheet//group//group[2]//label" position="before">
<!-- <field name="process_state"/> -->
<field name="production_type" readonly="1"/>
<field name="state" readonly="1"/>
<!-- <field name="process_state"/> -->
</xpath>
<xpath expr="//sheet//group//group//div[3]" position="after">
<field name="production_type" readonly="1"/>
<field name="production_product_type" invisible="1"/>
<field name="manual_quotation" readonly="1"
attrs="{'invisible': ['|', ('production_type', 'not in', ['自动化产线加工', '人工线下加工']), ('production_product_type', '!=', '成品')]}"/>
@@ -455,7 +455,7 @@
<field name="inherit_id" ref="mrp.mrp_production_workorder_tree_editable_view"/>
<field name="arch" type="xml">
<xpath expr="//tree" position="attributes">
<attribute name="default_order">create_date desc</attribute>
<attribute name="default_order">sequence,create_date desc</attribute>
<attribute name="decoration-warning">delivery_warning == 'warning'</attribute>
<attribute name="decoration-danger">delivery_warning == 'overdue'</attribute>
</xpath>

View File

@@ -4,30 +4,32 @@
name="Manufacturing"
groups="mrp.group_mrp_user,mrp.group_mrp_manager,sf_base.group_sf_mrp_user,sf_base.group_sf_mrp_manager"
web_icon="mrp,static/description/icon.svg"
sequence="145">
sequence="145"/>
<menuitem id="mrp.menu_mrp_manufacturing"
name="Operations"
sequence="10"/>
<menuitem id="mrp.menu_mrp_manufacturing"
name="Operations"
parent="mrp.menu_mrp_root"
sequence="10"/>
<menuitem id="mrp.mrp_planning_menu_root"
name="Planning"
sequence="15"/>
<menuitem id="mrp.mrp_planning_menu_root"
name="Planning"
parent="mrp.menu_mrp_root"
sequence="15"/>
<menuitem id="mrp.enu_mrp_bom"
name="Products"
sequence="20"/>
<menuitem id="mrp.menu_mrp_bom"
name="Products"
parent="mrp.menu_mrp_root"
sequence="20"/>
<menuitem id="mrp.menu_mrp_reporting"
name="Reporting"
sequence="25"/>
<menuitem id="mrp.menu_mrp_configuration"
name="Configuration"
groups="mrp.group_mrp_manager,sf_base.group_sf_mrp_manager"
sequence="100"/>
</menuitem>
<menuitem id="mrp.menu_mrp_reporting"
name="Reporting"
parent="mrp.menu_mrp_root"
sequence="25"/>
<menuitem id="mrp.menu_mrp_configuration"
name="Configuration"
parent="mrp.menu_mrp_root"
groups="mrp.group_mrp_manager,sf_base.group_sf_mrp_manager"
sequence="100"/>
</odoo>

View File

@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- 工作中心看板 -->
<record id="mrp_production_view_form_inherit_maintenance" model="ir.ui.view">
<!-- <record id="mrp_production_view_form_inherit_maintenance" model="ir.ui.view">
<field name="name">mrp.production.view.form.inherit.maintenance</field>
<field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
<field name="arch" type="xml">
<field name="arch" type="xml"> -->
<!-- <button name="action_cancel" position="before"> -->
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
<!-- </button> -->
<div name="button_box" position="inside">
<!-- <div name="button_box" position="inside">
<button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench"
attrs="{'invisible': [('maintenance_count', '=', 0)]}"
context="{'search_default_production_id': active_id}">
@@ -22,7 +22,7 @@
</button>
</div>
</field>
</record>
</record> -->
<record id="custom_model_form_view_inherit" model="ir.ui.view">
<field name="name">custom.model.form.view.inherit</field>
@@ -451,6 +451,7 @@
</div>
<field name="product_id" position="after">
<field name="model_file" string="产品模型" readonly="1" widget="Viewer3D" attrs="{'invisible': [('model_file', '=', False)]}"/>
<field name="model_id" readonly="1"/>
<field name="glb_url" widget="Viewer3D" string="模型" readonly="1" force_save="1"
attrs="{'invisible': [('glb_url', '=', False)]}"/>
</field>

View File

@@ -4,10 +4,11 @@
<record model="ir.ui.view" id="view_product_template_form_inherit_sf_manufacturing">
<field name="name">product.template.product.form.inherit.sf_manufacture</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="sf_dlm_management.view_product_template_only_form_inherit_sf"/>
<field name="inherit_id" ref="sf_sale.view_product_template_form_inherit_sf"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='general_information']/group/group[@name='group_standard_price']/field[@name='barcode']" position="after">
<field name="model_id" readonly="1"/>
<xpath expr="//page[@name='general_information']/group/group[@name='group_standard_price']/field[@name='product_tag_ids']" position="after">
<field name="categ_type" invisible="1"/>
<field name="model_id" readonly="1" attrs="{'invisible': [('categ_type', '!=', '成品')]}"/>
</xpath>
</field>
</record>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_warehouse_orderpoint_tree_editable_inherit" model="ir.ui.view">
<field name="name">补货</field>
<field name="model">stock.warehouse.orderpoint</field>
<field name="inherit_id" ref="stock.view_warehouse_orderpoint_tree_editable"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='qty_to_order']" position="after">
<field name="origin"/>
</xpath>
</field>
</record>
<!-- 继承补货单的搜索视图 -->
</data>
</odoo>

View File

@@ -213,11 +213,11 @@ class ReworkWizard(models.TransientModel):
self.production_id.get_new_program(panel_name)
if self.reprogramming_num >= 0 and self.programming_state == '已下发':
# ============= 处理CNC加工加工工单的 CNC程序和cmm程序 信息=============
for cnc_work in new_work_ids.filtered(lambda wk: wk.name == 'CNC加工'):
for cnc_work in new_work_ids.filtered(lambda wk: wk.name == 'CNC加工' or wk.name == '人工线下加工'):
ret = {'programming_list': []}
old_cnc_rework = max(self.production_id.workorder_ids.filtered(
lambda crw: crw.processing_panel == cnc_work.processing_panel
and crw.state == 'rework' and crw.routing_type == 'CNC加工'),
and crw.state == 'rework' and (crw.routing_type == 'CNC加工' or crw.routing_type == '人工线下加工')),
key=lambda w: w.create_date
)
# 获取当前工单的CNC程序和cmm程序
@@ -259,7 +259,7 @@ class ReworkWizard(models.TransientModel):
new_cnc_workorder = self.production_id.workorder_ids.filtered(
lambda ap1: ap1.processing_panel == cnc_work.processing_panel
and ap1.state not in (
'rework', 'done') and ap1.routing_type == 'CNC加工'
'rework', 'done') and (ap1.routing_type == 'CNC加工' or ap1.routing_type == '人工线下加工')
)
if not new_cnc_workorder.cnc_ids:
new_cnc_workorder.write({
@@ -268,6 +268,8 @@ class ReworkWizard(models.TransientModel):
'cmm_ids': new_cnc_workorder.cmm_ids.sudo()._json_cmm_program(
cnc_work.processing_panel, ret),
'cnc_worksheet': old_cnc_rework.cnc_worksheet})
# 复制装夹图纸
new_cnc_workorder.processing_drawing = old_cnc_rework.processing_drawing
# ========== 处理装夹预调 【装夹图纸】 数据 ================
for new_pre_work in new_pre_workorder_ids:
pre_rework = max(self.production_id.workorder_ids.filtered(
@@ -303,18 +305,22 @@ class ReworkWizard(models.TransientModel):
@api.onchange('production_id')
def onchange_processing_panel_id(self):
for item in self:
panel_ids = []
domain = [('id', '=', False)]
production_id = item.production_id
if production_id:
if self.env.user.has_group('sf_base.group_sf_order_user'):
panel_ids = []
panel_arr = production_id.product_id.model_processing_panel
if panel_arr is False:
break
for p in production_id.detection_result_ids.filtered(
lambda ap1: ap1.handle_result == '待处理'):
if p.processing_panel is not False and p.processing_panel not in panel_arr:
panel_arr += ','.join(p.processing_panel)
if len(panel_arr)>0:
panel_arr += ','.join(p.processing_panel)
else:
panel_arr = p.processing_panel
for item in panel_arr.split(','):
panel = self.env['sf.processing.panel'].search(
[('name', 'ilike', item)])

View File

@@ -3,6 +3,9 @@ import logging
import os
import json
import base64
import traceback
from odoo import http, fields, models
from odoo.http import request
from odoo.addons.sf_base.controllers.controllers import MultiInheritController
@@ -95,7 +98,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
logging.info('panel_file_path:%s' % panel_file_path)
# 向编程单中添加二维码
request.env['printing.utils'].add_qr_code_to_pdf(panel_file_path, model_id, "扫码获取工单")
request.env['printing.utils'].add_qr_code_to_pdf(panel_file_path, model_id, "模型ID%s" % model_id)
cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
pre_workorder = productions.workorder_ids.filtered(
lambda ap: ap.routing_type in ['装夹预调', '人工线下加工'] and ap.state not in ['done', 'rework'
@@ -273,7 +276,8 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
except Exception as e:
res = {'status': -1, 'message': '系统解析失败'}
request.cr.rollback()
logging.info('get_cnc_processing_create error:%s' % e)
traceback_error = traceback.format_exc()
logging.error("get_cnc_processing_create error:%s" % traceback_error)
return json.JSONEncoder().encode(res)

View File

@@ -1135,8 +1135,6 @@ class sfProductionProcessParameter(models.Model):
[("code", '=', item['code']), ('active', 'in', [True, False])])
process = self.env['sf.production.process'].search(
[('code', '=', item['process_id_code'])], limit=1)
production_process_parameter = self.search(
[("code", '=', item['code']), ('active', 'in', [True, False])])
if not production_process_parameter:
production_process_parameter = self.create({
"name": item['name'],

View File

@@ -228,7 +228,7 @@ class sf_production_plan(models.Model):
"""
排程方法
"""
self.deal_processing_schedule(self.date_planned_start)
self.deal_processing_schedule(self[0].date_planned_start)
for record in self:
if not record.production_line_id:
raise ValidationError("未选择生产线")

View File

@@ -5,6 +5,7 @@ from odoo import fields, models, api
from odoo.exceptions import ValidationError
from datetime import datetime
from odoo.addons.sf_base.commons.common import Common
from odoo.tools import float_round
class QualityCheck(models.Model):
@@ -125,10 +126,86 @@ class QualityCheck(models.Model):
# todo 需修改
val = ['0037818516']
logging.info('获取到的工单信息%s' % val)
r = requests.post(crea_url, json=val, headers=headers)
# r = requests.post(crea_url, json=val, headers=headers)
r = self.env['api.request.log'].log_request(
'get',
crea_url,
name='零件特采',
responser='中控系统',
json=val,
headers=headers
)
ret = r.json()
logging.info('_register_quality_check:%s' % ret)
if ret['Succeed']:
return "零件特采发送成功"
else:
raise ValidationError("零件特采发送失败")
@api.model_create_multi
def create(self, vals_list):
for val in vals_list:
if 'point_id' in val and 'measure_on' not in val:
# 如果没有控制方式字段,则从检查点读取质量方式
point_id = self.env['quality.point'].browse(val['point_id'])
val.update({'measure_on': point_id.measure_on})
return super(QualityCheck, self).create(vals_list)
@api.depends('total_qty','testing_percentage_within_lot', 'is_lot_tested_fractionally')
def _compute_workorder_qty_to_test(self):
for qc in self:
if qc.is_lot_tested_fractionally:
rounding = qc.product_id.uom_id.rounding if qc.product_id.uom_id else 0.01
qc.workorder_qty_to_test = float_round(float(qc.total_qty) * qc.testing_percentage_within_lot / 100,
precision_rounding=rounding, rounding_method="UP")
else:
qc.workorder_qty_to_test = qc.total_qty
@api.depends('picking_id', 'workorder_id')
def _compute_total_qty(self):
super(QualityCheck, self)._compute_total_qty()
for qc in self:
if not qc.picking_id and qc.workorder_id:
qc.total_qty = qc.workorder_id.production_id.product_qty
@api.depends('workorder_qty_to_test')
def _compute_workorder_qty_tested(self):
for qc in self:
qc.workorder_qty_tested = qc.workorder_qty_to_test
workorder_qty_to_test = fields.Float('应检', compute='_compute_workorder_qty_to_test', store=True)
workorder_qty_tested = fields.Float('已检', compute='_compute_workorder_qty_tested', store=True)
workorder_qty_test_failed = fields.Float('不合格数')
@api.onchange('total_qty', 'workorder_qty_test_failed', 'workorder_qty_to_test', 'workorder_qty_tested')
def _onchage_qty(self):
for record in self:
if record.total_qty and record.workorder_qty_to_test and record.workorder_qty_to_test > float(record.total_qty):
record.workorder_qty_to_test = float(record.total_qty)
return {
'warning': {
'title': '警告',
'message': '待检数量不能超过总数量'
}
}
if record.workorder_qty_to_test and record.workorder_qty_tested and record.workorder_qty_tested > record.workorder_qty_to_test:
record.workorder_qty_tested = record.workorder_qty_to_test
return {
'warning': {
'title': '警告',
'message': '已检数量不能超过待检数量'
}
}
if record.workorder_qty_tested and record.workorder_qty_test_failed and record.workorder_qty_test_failed > record.workorder_qty_tested:
record.workorder_qty_test_failed = record.workorder_qty_tested
return {
'warning': {
'title': '警告',
'message': '不合格数量不能超过已检数量'
}
}

View File

@@ -88,6 +88,42 @@
<button name="do_cancel_publish" string="取消发布" type="object" class="btn-primary" confirm="确定取消发布吗?" attrs="{'invisible': ['|',('is_out_check', '=', False), ('publish_status', '!=', 'published')]}"/>
<button name="do_re_publish" string="重新发布" type="object" class="btn-primary" attrs="{'invisible': ['|', ('is_out_check', '=', False), ('publish_status', '!=', 'canceled')]}"/>
</xpath>
<xpath expr="//field[@name='total_qty']" position="attributes">
<attribute name="attrs">{
'invisible': ['&amp;', '|', ('measure_on', '!=', 'product'), ('is_out_check', '=', False), '|', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False)],
'readonly': ['|', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False)],
'on_change': ['|', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False)]
}</attribute>
</xpath>
<xpath expr="//field[@name='total_qty']" position="after">
<label for="workorder_qty_to_test"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/>
<div class="o_row"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}">
<field name="workorder_qty_to_test" attrs="{'readonly': 0, 'on_chnage': 1}"/>
<field name="uom_id"/>
</div>
<label for="workorder_qty_tested"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/>
<div class="o_row"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}">
<field name="workorder_qty_tested" attrs="{'readonly': [('quality_state', '!=', 'none')], 'on_chnage': 1}"/>
<field name="uom_id"/>
</div>
<label for="workorder_qty_test_failed"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/>
<div class="o_row"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}">
<field name="workorder_qty_test_failed" attrs="{'readonly': [('quality_state', '!=', 'none')], 'on_chnage': 1}"/>
<field name="uom_id"/>
</div>
</xpath>
<xpath expr="//label[@for='qty_tested']" position="attributes">
<attribute name="attrs">{'invisible': ['|', '|', ('measure_on', '!=', 'move_line'), ('is_lot_tested_fractionally', '=', False), '&amp;', ('measure_on', '=', 'move_line'), ('workorder_id', '!=', False)]}</attribute>
</xpath>
<xpath expr="//div[@class='o_row'][.//field[@name='qty_tested']]" position="attributes">
<attribute name="attrs">{'invisible': ['|', '|', ('measure_on', '!=', 'move_line'), ('is_lot_tested_fractionally', '=', False), '&amp;', ('measure_on', '=', 'move_line'), ('workorder_id', '!=', False)]}</attribute>
</xpath>
</field>
</record>

View File

@@ -271,7 +271,7 @@ class ResaleOrderLine(models.Model):
embryo_redundancy_id = fields.Many2one('sf.embryo.redundancy', '坯料冗余')
manual_quotation = fields.Boolean('人工编程', default=False)
model_url = fields.Char('模型文件地址')
model_id = fields.Char('模型id')
model_id = fields.Char('模型ID')
delivery_end_date = fields.Date('交货截止日期')
@@ -323,7 +323,7 @@ class RePurchaseOrder(models.Model):
contract_summary = fields.Text(string='合同概况')
# 选择是否为紧急采购
urgent_purchase = fields.Selection([('no', ''), ('yes', '')], string='紧急采购', default='no')
urgent_purchase = fields.Selection([('no', ''), ('yes', '')], string='紧急采购', default='yes')
@api.depends('origin')
def _compute_purchase_type(self):
@@ -343,6 +343,9 @@ class RePurchaseOrder(models.Model):
if order_line.product_id.id in product_list:
purchase.purchase_type = 'outsourcing'
break
if purchase.order_line[0].product_id.categ_id.name == '坯料':
if purchase.order_line[0].product_id.materials_type_id.gain_way == '外协':
purchase.purchase_type = 'outsourcing'
request_lines = self.order_line.mapped('purchase_request_lines')
# 检查是否存在 is_subcontract 为 True 的行
if any(line.is_subcontract for line in request_lines):
@@ -390,10 +393,11 @@ class RePurchaseOrder(models.Model):
# route_ids
result.append({
"product_id": server_template.product_variant_id.id,
'related_product': production.product_id.id,
"name": production.procurement_group_id.name,
"date_required": fields.Datetime.now(),
"product_uom_id":server_template.uom_id.id,
"product_qty": 1,
"product_qty": production.product_qty,
"request_id": False,
"move_dest_ids": False,
"orderpoint_id": False,
@@ -416,7 +420,7 @@ class RePurchaseOrder(models.Model):
('detailed_type', '=', 'service')])
server_product_process.append((0, 0, {
'product_id': server_template.product_variant_id.id,
'product_qty': 1,
'product_qty': production.product_uom_qty,
'product_uom': server_template.uom_id.id,
'related_product': production.product_id.id,
'manual_part_number': pp.part_number,

View File

@@ -107,6 +107,7 @@
<field name="glb_url" widget="Viewer3D" optional="show"
string="模型文件" readonly="1" attrs="{'column_invisible': [('parent.model_display_version', '!=', 'v2')], 'isInList': True}"/>
<field name="part_name" optional="show"/>
<field name="model_id" optional="hide"/>
</xpath>
<xpath expr="//field[@name='order_line']/tree/field[@name='price_subtotal']" position="after">
<field name="remark"/>
@@ -359,10 +360,10 @@
<field name="categ_id" position="replace">
<field name='categ_id' invisible="1"/>
</field>
<field name="product_tag_ids" position="after">
<!-- <field name="product_tag_ids" position="after">
<field name="default_code" attrs="{'invisible': [('product_variant_count', '&gt;', 1)]}"/>
<field name="barcode" attrs="{'invisible': [('product_variant_count', '&gt;', 1)]}"/>
</field>
</field> -->
</field>
</record>
<record id="sale.product_template_action" model="ir.actions.act_window">

View File

@@ -4,12 +4,14 @@ import json
import base64
from odoo import http
from odoo.http import request
from odoo.addons.sf_base.decorators.api_log import api_log
class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/ToolGroup', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*")
@api_log('刀具组', requester='中控系统')
def get_functional_tool_groups_Info(self, **kw):
"""
刀具组接口

View File

@@ -51,7 +51,15 @@ class SfMaintenanceEquipment(models.Model):
headers = {'Authorization': config['center_control_Authorization']}
crea_url = config['center_control_url'] + "/AutoDeviceApi/GetToolInfos"
params = {"DeviceId": self.name}
r = requests.get(crea_url, params=params, headers=headers)
# r = requests.get(crea_url, params=params, headers=headers)
r = self.env['api.request.log'].log_request(
'get',
crea_url,
name='机床刀库',
responser='中控系统',
params=params,
headers=headers
)
ret = r.json()
logging.info('机床刀库register_equipment_tool():%s' % ret)
datas = ret['Datas']

View File

@@ -514,7 +514,15 @@ class ShelfLocation(models.Model):
crea_url = config['center_control_url'] + "/AutoDeviceApi/GetLocationInfos"
params = {'DeviceId': device_id}
r = requests.get(crea_url, params=params, headers=headers)
# r = requests.get(crea_url, params=params, headers=headers)
r = self.env['api.request.log'].log_request(
'get',
crea_url,
name='库位信息',
responser='中控系统',
params=params,
headers=headers
)
ret = r.json()
@@ -812,40 +820,49 @@ class SfStockMoveLine(models.Model):
# # 从目标stock.move对象获取目标stock.picking对象
# dest_picking = dest_move.picking_id if dest_move else False
# # 现在dest_picking就是current_picking的下一步
# 添加所有需要的依赖字段
@api.depends('location_id')
def _compute_current_location_id(self):
# 批量获取所有相关记录的picking
pickings = self.mapped('picking_id')
# 构建源picking的移库行与目标位置的映射
origin_location_map = {}
for picking in pickings:
# 获取源picking
origin_move = picking.move_ids[:1].move_orig_ids[:1]
if not origin_move:
continue
origin_picking = origin_move.picking_id
if not origin_picking:
continue
# 为每个picking构建lot_id到location的映射
origin_location_map[picking.id] = {
move_line.lot_id.id: move_line.destination_location_id
for move_line in origin_picking.move_line_ids.filtered(
lambda ml: ml.destination_location_id and ml.lot_id
)
}
# 批量更新current_location_id
for record in self:
# 使用record代替self来引用当前遍历到的记录
logging.info('record.picking_id.name: %s' % record.picking_id.name)
logging.info('record.env: %s' % record.env['stock.picking'].search([('name', '=', record.picking_id.name)]))
# 获取当前的stock.picking对象
current_picking = record.env['stock.picking'].search([('name', '=', record.picking_id.name)], limit=1)
# 获取当前picking的第一个stock.move对象
current_move = current_picking.move_ids[0] if current_picking.move_ids else False
# 如果存在相关的stock.move对象
if current_move:
# 获取源stock.move对象
origin_move = current_move.move_orig_ids[0] if current_move.move_orig_ids else False
# 从源stock.move对象获取源stock.picking对象
origin_picking = origin_move.picking_id if origin_move else False
# 如果前一个调拨单有目标货位
if origin_picking:
for i in current_picking.move_line_ids:
for j in origin_picking.move_line_ids:
if j.destination_location_id and i.lot_id == j.lot_id:
# 更新当前记录的current_location_id字段
record.current_location_id = j.destination_location_id
# # 获取目标stock.move对象
# dest_move = current_move.move_dest_ids[0] if current_move.move_dest_ids else False
#
# # 从目标stock.move对象获取目标stock.picking对象
# dest_picking = dest_move.picking_id if dest_move else False
# # 现在dest_picking就是current_picking的下一步
current_picking = record.picking_id
if not current_picking:
record.current_location_id = False
continue
# 获取当前picking对应的lot_location映射
lot_dest_map = origin_location_map.get(current_picking.id, {})
# 查找匹配的lot_id
for move_line in current_picking.move_line_ids:
if move_line.lot_id and move_line.lot_id.id in lot_dest_map:
record.current_location_id = lot_dest_map[move_line.lot_id.id]
break
else:
record.current_location_id = False
# 是一张单据一张单据往下走的,所以这里的目标货位是上一张单据的当前货位,且这样去计算是可以的。
@api.depends('location_dest_id')