Compare commits

..

106 Commits

Author SHA1 Message Date
胡尧
6ed5de6400 修改出厂检验报告预览数据 2025-06-09 09:08:26 +08:00
胡尧
03ec94d223 修复点击智能按钮点击的问题 2025-06-06 09:33:23 +08:00
胡尧
2ea24f2049 修改制造订单的采购申请只能按钮判断规则 2025-06-06 09:16:41 +08:00
胡尧
0b5415dc47 在出厂检验报告验证数量时,增加产品的匹配 2025-06-06 08:37:38 +08:00
胡尧
4e1be6f4d5 修复二次弹窗不能刷新页面的问题,但是js方法还需要修改 2025-06-06 08:29:25 +08:00
胡尧
540b7bcbea 修改质检单字段显示 2025-06-05 10:19:58 +08:00
禹翔辉
001900bd65 Accept Merge Request #2164: (feature/销售合同优化 -> develop)
Merge Request: Merge branch 'feature/采购消息通知' into feature/销售合同优化

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2164
2025-06-05 09:19:28 +08:00
yuxianghui
0204e0e24f Merge branch 'feature/采购消息通知' into feature/销售合同优化 2025-06-05 09:17:26 +08:00
yuxianghui
2b3a2dd21c 1、处理返工报错,2、保存的合同文档添加对应销售订单号 2025-06-05 09:16:02 +08:00
胡尧
57acad4716 Accept Merge Request #2163: (feature/6694 -> develop)
Merge Request: 调整采购申请菜单位置

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2163?initial=true
2025-06-05 09:15:46 +08:00
胡尧
9f97c82a46 调整采购申请菜单位置 2025-06-05 09:15:25 +08:00
胡尧
7cafddd345 Accept Merge Request #2162: (feature/6694 -> develop)
Merge Request: 修改出厂检验报告重新发布按钮,增加确认提示

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2162?initial=true
2025-06-05 08:59:10 +08:00
胡尧
c796697a8e 修改出厂检验报告重新发布按钮,增加确认提示 2025-06-05 08:56:34 +08:00
胡尧
ee523e9aac Accept Merge Request #2161: (feature/6694 -> develop)
Merge Request: 出厂检验报告发布按钮增加判断

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2161?initial=true
2025-06-04 17:36:24 +08:00
胡尧
87fdc7bf74 出厂检验报告发布按钮增加判断 2025-06-04 17:35:57 +08:00
禹翔辉
e7d84e9df2 Accept Merge Request #2160: (feature/采购消息通知 -> develop)
Merge Request: Merge branch 'feature/销售订单优化' into feature/采购消息通知

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2160
2025-06-04 16:28:15 +08:00
yuxianghui
b7642d1e0f Merge branch 'feature/销售订单优化' into feature/采购消息通知 2025-06-04 16:26:03 +08:00
yuxianghui
05ffbdcc78 采购申请、待质检消息通知优化 2025-06-04 16:24:52 +08:00
胡尧
4b8d00ec1d Accept Merge Request #2159: (feature/6694 -> develop)
Merge Request: 修复质检字段显示错误问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2159?initial=true
2025-06-04 16:06:16 +08:00
胡尧
d2dbf4f986 修复质检字段显示错误问题 2025-06-04 16:05:51 +08:00
禹翔辉
edfd59468f Accept Merge Request #2158: (feature/销售订单优化 -> develop)
Merge Request: Merge branch 'feature/消息通知优化' into feature/销售订单优化

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2158
2025-06-04 11:36:18 +08:00
yuxianghui
d7f04b61b5 Merge branch 'feature/消息通知优化' into feature/销售订单优化 2025-06-04 11:33:04 +08:00
yuxianghui
573b50da68 销售订单添加合同字段,销售订单form页面新增合同page页,优化/api/bfm_process_order/list接口添加合同信息处理。 2025-06-04 11:31:28 +08:00
胡尧
2e0dfc5b02 Accept Merge Request #2157: (feature/6694 -> develop)
Merge Request: 修改没有状态码时默认为200

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2157?initial=true
2025-06-04 09:17:01 +08:00
胡尧
18cdc39719 修改没有状态码时默认为200 2025-06-04 09:16:28 +08:00
胡尧
14fa88da01 Accept Merge Request #2156: (feature/6694 -> develop)
Merge Request: 修改编程返回记录申请人不对的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2156?initial=true
2025-05-30 10:27:10 +08:00
胡尧
162814411f 修改编程返回记录申请人不对的问题 2025-05-30 10:26:34 +08:00
胡尧
49e4c88d83 Accept Merge Request #2155: (feature/6694 -> develop)
Merge Request: 解决下发编程单报错的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2155?initial=true
2025-05-29 16:00:16 +08:00
胡尧
db81114a07 解决下发编程单报错的问题 2025-05-29 15:59:48 +08:00
禹翔辉
e019383187 Accept Merge Request #2154: (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/2154
2025-05-29 14:19:17 +08:00
yuxianghui
4a09148b53 质检消息通知优化 2025-05-29 14:10:36 +08:00
胡尧
ab139daf02 Accept Merge Request #2153: (feature/6694 -> develop)
Merge Request: 出厂检验报告需求

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2153?initial=true
2025-05-29 10:18:49 +08:00
胡尧
24d83b70d2 出厂检验报告需求 2025-05-29 10:18:18 +08:00
禹翔辉
307510e1ab Accept Merge Request #2152: (feature/消息通知优化 -> develop)
Merge Request: Merge branch 'feature/采购申请消息通知' into feature/消息通知优化

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2152
2025-05-28 16:03:52 +08:00
yuxianghui
9b7222961c Merge branch 'feature/采购申请消息通知' into feature/消息通知优化 2025-05-28 15:58:06 +08:00
yuxianghui
3304398c4c 1、重写待质检提醒消息通知;2、优化采购申请已批准消息通知 2025-05-28 15:56:42 +08:00
胡尧
074e59cee4 Accept Merge Request #2151: (feature/6694 -> develop)
Merge Request: 修改编程下发,修改编程记录

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2151?initial=true
2025-05-28 14:08:43 +08:00
胡尧
7ab8ab47ac 修改编程下发,修改编程记录 2025-05-28 14:08:10 +08:00
禹翔辉
3a89ebff60 Accept Merge Request #2150: (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/2150
2025-05-28 09:58:22 +08:00
yuxianghui
d0ee2a6564 添加采购申请状态为待批准时的消息通知 2025-05-28 09:54:27 +08:00
黄焱
257d4a3b0a Accept Merge Request #2149: (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/2149?initial=true
2025-05-27 09:13:57 +08:00
胡尧
40fe1f15a2 Accept Merge Request #2148: (feature/6694 -> develop)
Merge Request: 制造订单的编程记录修改

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2148?initial=true
2025-05-26 09:32:06 +08:00
胡尧
30a6e5bf2e 制造订单的编程记录修改 2025-05-26 09:31:47 +08:00
禹翔辉
794ea0cbb0 Accept Merge Request #2147: (feature/返工解绑rfid -> develop)
Merge Request: 1、优化返工装夹预调工单时,清理rfid问题;2、优化库位信息接口数据处理方式

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2147
2025-05-23 17:17:06 +08:00
yuxianghui
619285608d 1、优化返工装夹预调工单时,清理rfid问题;2、优化库位信息接口数据处理方式 2025-05-23 17:13:12 +08:00
管欢
433d5d63b7 Accept Merge Request #2146: (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/2146
2025-05-23 15:19:30 +08:00
guanhuan
38109028d4 采购申请关闭修改 2025-05-23 15:13:01 +08:00
guanhuan
cf16a9dd59 采购申请关闭修改 2025-05-23 14:53:34 +08:00
胡尧
4fcbeb30cf Accept Merge Request #2143: (feature/6694 -> develop)
Merge Request: 调整采购单菜单顺序

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2143
2025-05-23 14:47:58 +08:00
胡尧
be8fbca9aa 解决冲突 2025-05-23 14:47:43 +08:00
胡尧
b393951968 去掉采购申请列表默认筛选条件 2025-05-23 14:45:57 +08:00
管欢
6add565a98 Accept Merge Request #2144: (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/2144
2025-05-23 14:22:18 +08:00
guanhuan
1b0dd96b40 采购申请关闭修改 2025-05-23 14:20:48 +08:00
胡尧
89f8718fe1 当询价单确认为采购单时,根据采购申请明细行变为采购单数量来完成采购申请 2025-05-23 14:20:38 +08:00
胡尧
7941c1981c 撤回采购申请视图修改 2025-05-23 11:37:21 +08:00
胡尧
f31e25b3b1 调整采购单菜单顺序 2025-05-23 11:20:17 +08:00
胡尧
ae7e49e307 Accept Merge Request #2142: (feature/6694 -> develop)
Merge Request: 增加分组

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2142?initial=true
2025-05-23 11:11:10 +08:00
胡尧
51c8287bbc 增加分组 2025-05-23 11:10:46 +08:00
胡尧
942d6661f2 修改api接口内容 2025-05-23 10:51:39 +08:00
胡尧
51ae598aac Accept Merge Request #2141: (feature/6694 -> develop)
Merge Request: 取消采购单时,如果该采购单关联的库存移动是由采购申请创建的,则不取消

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2141
2025-05-23 09:43:07 +08:00
胡尧
83229c9ab1 解决冲突 2025-05-23 09:42:52 +08:00
管欢
e62f933ca4 Accept Merge Request #2140: (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/2140
2025-05-23 09:41:03 +08:00
胡尧
1f8e118965 取消采购单时,如果该采购单关联的库存移动是由采购申请创建的,则不取消 2025-05-23 09:39:38 +08:00
guanhuan
2c52372b0a 采购申请隐藏处理中按钮 2025-05-22 11:44:14 +08:00
禹翔辉
5cf3d399f4 Accept Merge Request #2139: (feature/销售订单链接优化 -> develop)
Merge Request: 处理 sf-销售-委外的按钮关联订单数据显示不全 问题

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2139
2025-05-22 11:37:13 +08:00
yuxianghui
e9fc78186e 处理 sf-销售-委外的按钮关联订单数据显示不全 问题 2025-05-22 11:35:53 +08:00
胡尧
a2b2faaa95 Accept Merge Request #2138: (feature/6694 -> develop)
Merge Request: 修改欠单逻辑

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2138?initial=true
2025-05-22 11:10:28 +08:00
禹翔辉
4c7208784f Accept Merge Request #2137: (feature/中控接口优化 -> develop)
Merge Request: 1、获取日计划接口优化;2、库位信息接口优化;

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2137
2025-05-22 11:10:12 +08:00
yuxianghui
981569170c Merge branch 'feature/销售委外加工按钮优化_1' into feature/中控接口优化 2025-05-22 11:04:48 +08:00
胡尧
6bf666ac18 修改欠单逻辑 2025-05-22 11:04:12 +08:00
yuxianghui
3a9cd3f39d 1、获取日计划接口优化;2、货位信息接口优化; 2025-05-22 11:04:06 +08:00
胡尧
45b62abcbe Accept Merge Request #2136: (feature/6694 -> develop)
Merge Request: 增加采购申请审批排除字段

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2136?initial=true
2025-05-20 16:51:09 +08:00
胡尧
87740dbee3 增加采购申请审批排除字段 2025-05-20 16:49:08 +08:00
胡尧
a323acf7fc Accept Merge Request #2135: (feature/6694 -> develop)
Merge Request: 取消采购申请部分确认生成的反向单据

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2135?initial=true
2025-05-20 16:09:06 +08:00
胡尧
c23715a1ef 取消采购申请部分确认生成的反向单据 2025-05-20 16:08:03 +08:00
yuxianghui
896c1ad3a7 更新库位信息接口,优化获取到数据后的数据处理逻辑。 2025-05-20 13:46:43 +08:00
管欢
1856a1a4ef Accept Merge Request #2134: (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/2134
2025-05-19 11:04:44 +08:00
guanhuan
8bd09cddf0 采购申请按钮优化 2025-05-19 11:02:43 +08:00
guanhuan
52579673de 采购申请单完成操作优化 2025-05-19 08:54:02 +08:00
胡尧
31cd131993 Accept Merge Request #2133: (feature/6694 -> develop)
Merge Request: 在调拨单确认生成欠单时,修改对应采购申请明细行的目标移动

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2133?initial=true
2025-05-16 11:50:47 +08:00
胡尧
9f94a4e06f 在调拨单确认生成欠单时,修改对应采购申请明细行的目标移动 2025-05-16 11:44:06 +08:00
禹翔辉
e66c6b1e1b Accept Merge Request #2132: (feature/销售委外加工按钮优化_1 -> develop)
Merge Request: 处理  销售-外购,委外的按钮关联订单数据显示不全  bug

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2132
2025-05-16 09:21:46 +08:00
yuxianghui
e97d0af941 1 2025-05-16 09:18:25 +08:00
yuxianghui
65122d38d5 处理 销售-外购,委外的按钮关联订单数据显示不全 bug 2025-05-16 09:17:13 +08:00
胡尧
b626cbe217 Accept Merge Request #2131: (feature/6694 -> develop)
Merge Request: 解决库存单据零件图号零件名称不对的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2131?initial=true
2025-05-16 09:15:46 +08:00
胡尧
4b443e65f6 解决库存单据零件图号零件名称不对的问题 2025-05-16 09:14:37 +08:00
胡尧
14700d6bf0 Accept Merge Request #2130: (feature/6694 -> develop)
Merge Request: 解决工艺外协采购单不显示的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2130
2025-05-15 16:49:05 +08:00
胡尧
a3c0fd3ccf 解决工艺外协采购单不显示的问题 2025-05-15 16:48:34 +08:00
胡尧
a29265f17d 修改正则匹配成品名字序号 2025-05-15 16:39:57 +08:00
胡尧
0821ed021a Accept Merge Request #2129: (feature/6694 -> develop)
Merge Request: 解决采购申请中零件图号不匹配的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2129?initial=true
2025-05-15 16:30:28 +08:00
胡尧
ac4883db66 解决采购申请中零件图号不匹配的问题 2025-05-15 16:29:33 +08:00
胡尧
5706aa0052 Accept Merge Request #2127: (feature/6694 -> develop)
Merge Request: 增加零件图号

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2127?initial=true
2025-05-15 16:12:18 +08:00
胡尧
f780e4f7ce 增加零件图号 2025-05-15 16:11:31 +08:00
胡尧
e6d8ebb7b3 修改 2025-05-15 14:39:40 +08:00
胡尧
3663e04b34 Accept Merge Request #2126: (feature/6694 -> develop)
Merge Request: 解决产品模板复制的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2126?initial=true
2025-05-15 12:14:51 +08:00
胡尧
c2e4085b50 解决产品模板复制的问题 2025-05-15 12:14:33 +08:00
胡尧
33c881b12f Accept Merge Request #2125: (feature/6694 -> develop)
Merge Request: 回退工艺外协代码

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2125?initial=true
2025-05-15 11:07:51 +08:00
胡尧
c80e12d731 回退工艺外协代码 2025-05-15 11:07:22 +08:00
胡尧
5446ef18e2 回退代码 2025-05-15 10:36:38 +08:00
胡尧
9c4d545915 Merge branch 'develop' into feature/6694 2025-05-15 10:33:42 +08:00
胡尧
f6d8cb6267 编程单增加零件图号 2025-05-15 08:45:02 +08:00
胡尧
c898e02860 解决制造申请字段未复制到成品中 2025-05-14 17:02:04 +08:00
胡尧
5477582a69 修改中控日志接口授权为none 2025-05-14 16:09:06 +08:00
胡尧
9cb22d810e 增加接口日志 2025-05-12 15:14:04 +08:00
胡尧
61bcd72a41 表面工艺外协调拨单数量问题 2025-05-09 17:24:24 +08:00
hyyy
51a8964b89 修复出厂检验质检单详情手动新增明细行后右边的【删除】按钮显示不全 2025-05-07 15:48:05 +08:00
72 changed files with 1463 additions and 808 deletions

View File

@@ -119,13 +119,24 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', {
this.listherHeaderBodyNum() this.listherHeaderBodyNum()
}) })
const treeModifiers = this.getFieldModifiers(this.props.archInfo.__rawArch); const treeModifiers = this.getFieldModifiers(this.props.archInfo.__rawArch);
if(treeModifiers) { if(treeModifiers) {
this.props.merge_key = treeModifiers.merge_key; if(treeModifiers.merge_fields) {
this.props.merge_fields = treeModifiers.merge_fields.split(','); this.props.merge_key = treeModifiers.merge_key;
const data = this.setColumns(this.props.merge_key); this.props.merge_fields = treeModifiers.merge_fields.split(',');
owl.onMounted(() => { const data = this.setColumns(this.props.merge_key);
this.mergeColumns(this.props.merge_fields, data) owl.onMounted(() => {
}) this.mergeColumns(this.props.merge_fields, data)
})
}
if(treeModifiers.pacthResize) {
owl.onPatched(() => {
this.columnWidths = null;
this.freezeColumnWidths();
})
}
} }
return this._super(...arguments); return this._super(...arguments);
}, },

View File

@@ -8,18 +8,21 @@
'category': 'purchase', 'category': 'purchase',
'depends': ['sf_manufacturing', 'purchase_request'], 'depends': ['sf_manufacturing', 'purchase_request'],
'data': [ 'data': [
'security/ir.model.access.csv',
'views/sale_order_view.xml', 'views/sale_order_view.xml',
'views/mrp_production.xml', 'views/mrp_production.xml',
'views/purchase_request_view.xml', 'views/purchase_request_view.xml',
'wizard/purchase_request_line_make_purchase_order_view.xml', 'wizard/purchase_request_line_make_purchase_order_view.xml',
'views/purchase_request_line_view.xml', 'views/purchase_request_line_view.xml',
'views/stock_picking_views.xml', 'views/stock_picking_views.xml',
'wizard/purchase_request_wizard_views.xml',
'views/purchase_request_menu_views.xml',
], ],
'assets': { 'assets': {
'web.assets_backend': [ 'web.assets_backend': [
'jikimo_purchase_request/static/src/**/*' 'jikimo_purchase_request/static/src/**/*'
], ],
}, },
'application': True, 'application': True,
'installable': True, 'installable': True,
'auto_install': False, 'auto_install': False,

View File

@@ -410,7 +410,7 @@ msgstr "显示名称"
#: model_terms:ir.ui.view,arch_db:purchase_request.view_purchase_request_form #: model_terms:ir.ui.view,arch_db:purchase_request.view_purchase_request_form
#: model_terms:ir.ui.view,arch_db:purchase_request.view_purchase_request_search #: model_terms:ir.ui.view,arch_db:purchase_request.view_purchase_request_search
msgid "Done" msgid "Done"
msgstr "完成" msgstr "关闭"
#. module: purchase_request #. module: purchase_request
#: model:ir.model.fields,field_description:purchase_request.field_purchase_request_line__move_dest_ids #: model:ir.model.fields,field_description:purchase_request.field_purchase_request_line__move_dest_ids

View File

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

View File

@@ -1,4 +1,5 @@
from odoo import api, fields, models, _ from odoo import api, fields, models, _
from odoo.tools import float_compare
class PurchaseOrder(models.Model): class PurchaseOrder(models.Model):
@@ -13,4 +14,43 @@ class PurchaseOrder(models.Model):
('done', '完成'), ('done', '完成'),
('cancel', '取消'), ('cancel', '取消'),
('rejected', '已驳回') ('rejected', '已驳回')
], string='Status', readonly=True, index=True, copy=False, default='draft', tracking=True) ], string='Status', readonly=True, index=True, copy=False, default='draft', tracking=True)
def button_confirm(self):
res = super(PurchaseOrder, self).button_confirm()
# 取消反向调拨单
reverse_move_ids = self.env['stock.move'].search([
('origin', '=', self.name),
('purchase_line_id', '=', False),
('state', '!=', 'done')
])
if reverse_move_ids:
reverse_move_ids.picking_id.action_cancel()
return res
def button_cancel(self):
"""
将取消的采购订单关联的库存移动撤销
"""
move_ids = self.order_line.move_dest_ids.filtered(lambda move: move.state != 'done' and not move.scrapped)
res =super(PurchaseOrder, self).button_cancel()
if move_ids.mapped('created_purchase_request_line_id'):
move_ids.write({'state': 'waiting', 'is_done': False})
return res
def write(self, vals):
res = super(PurchaseOrder, self).write(vals)
if 'state' in vals and vals['state'] == 'purchase':
purchase_request = self.order_line.purchase_request_lines.request_id
if purchase_request:
finished = True
# 判断该采购申请所有明细行是否都完成
for purchase_request_line in purchase_request.line_ids:
finished_qty = sum(purchase_request_line.purchase_lines.filtered(lambda line: line.state == 'purchase').mapped('product_qty'))
if float_compare(finished_qty ,purchase_request_line.product_qty, precision_rounding=purchase_request_line.product_id.uom_id.rounding) < 0:
finished = False
break
if finished:
purchase_request.button_done()
return res

View File

@@ -1,13 +1,15 @@
import re import re
import ast import ast
from odoo import models, fields, api from odoo import models, fields, api, _
from itertools import groupby
from odoo.tools import float_compare
class PurchaseRequest(models.Model): class PurchaseRequest(models.Model):
_inherit = 'purchase.request' _inherit = 'purchase.request'
_description = '采购申请' _description = '采购申请'
# 为state添加取消状态 # 为state添加取消状态
state = fields.Selection( state = fields.Selection(
selection_add=[('cancel', '已取消')], selection_add=[('cancel', '已取消')],
ondelete={'cancel': 'set default'} # 添加 ondelete 策略 ondelete={'cancel': 'set default'} # 添加 ondelete 策略
@@ -29,6 +31,56 @@ class PurchaseRequest(models.Model):
action['context'] = origin_context action['context'] = origin_context
return action return action
def button_done(self):
product_qty_map = {key: sum(line.product_qty for line in group) for key, group in
groupby(self.line_ids, key=lambda x: x.product_id.id)}
lines = self.mapped("line_ids.purchase_lines.order_id")
# 采购单产品和数量
product_summary = {}
product_rounding = {}
if lines:
for line in lines:
for line_item in line.order_line:
product_id = line_item.product_id.id
qty = line_item.product_qty
product_rounding[product_id] = line_item.product_id.uom_id.rounding
if product_id in product_summary:
product_summary[product_id] += qty
else:
product_summary[product_id] = qty
# 校验产品数量
discrepancies = []
for product_id, qty in product_qty_map.items():
if product_id in product_summary:
if float_compare(product_summary[product_id], qty, precision_rounding=product_rounding[product_id]) < 0:
discrepancies.append((product_id, qty, product_summary[product_id]))
else:
discrepancies.append((product_id, qty, 0))
if discrepancies:
# 弹出提示框
message = "产品数量不一致:\n"
for product_id, required_qty, order_qty in discrepancies:
product_name = self.env['product.product'].browse(product_id).display_name # 获取产品名称
message += f"产品 {product_name},需求数量 {required_qty},关联采购订单数量 {order_qty}(含询价状态)\n"
# 添加确认框
message += "确认关闭?"
return {
'name': _('采购申请'),
'type': 'ir.actions.act_window',
'views': [(self.env.ref(
'jikimo_purchase_request.purchase_request_wizard_wizard_form_view').id,
'form')],
'res_model': 'purchase.request.wizard',
'target': 'new',
'context': {
'default_purchase_request_id': self.id,
'default_message': message,
}}
return super(PurchaseRequest, self).button_done()
class PurchaseRequestLine(models.Model): class PurchaseRequestLine(models.Model):
_inherit = 'purchase.request.line' _inherit = 'purchase.request.line'
_description = '采购申请明细' _description = '采购申请明细'
@@ -47,7 +99,8 @@ class PurchaseRequestLine(models.Model):
('outsourcing', "委外加工"), ('outsourcing', "委外加工"),
], string='供货方式', compute='_compute_supply_method', store=True) ], string='供货方式', compute='_compute_supply_method', store=True)
purchase_request_count = fields.Integer(string='采购申请数量', compute='_compute_purchase_request_count', readonly=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) purchase_count = fields.Integer(string="采购订单数量", compute="_compute_purchase_count", readonly=True)
@api.depends("purchase_lines") @api.depends("purchase_lines")
@@ -92,7 +145,7 @@ class PurchaseRequestLine(models.Model):
continue continue
if record.product_id.categ_id.name == '坯料': if record.product_id.categ_id.name == '坯料':
product_name = '' product_name = ''
match = re.search(r'(S\d{5}-\d)', record.product_id.name) match = re.search(r'(S\d{5}-\d+)', record.product_id.name)
# 如果匹配成功,提取结果 # 如果匹配成功,提取结果
if match: if match:
product_name = match.group(0) product_name = match.group(0)

View File

@@ -33,3 +33,15 @@ class StockPicking(models.Model):
'view_mode': 'tree,form', 'view_mode': 'tree,form',
}) })
return action return action
def _action_done(self):
res = super(StockPicking, self)._action_done()
# 将新产生的backorder对应上原来的采购申请明细行
backorder_ids = self.backorder_ids
if backorder_ids:
purchase_request_lines = self.move_ids.move_orig_ids.purchase_line_id.purchase_request_lines
if purchase_request_lines:
purchase_request_lines.move_dest_ids = [
(4, x.id) for x in backorder_ids.move_ids if x.product_id.id == purchase_request_lines.product_id.id
]
return res

View File

@@ -79,12 +79,4 @@ class StockRule(models.Model):
) )
res = super(StockRule, self)._run_buy(new_procurements) res = super(StockRule, self)._run_buy(new_procurements)
# 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved
origins = list(set([procurement[0].origin for procurement in procurements]))
for origin in origins:
pr_ids = self.env["purchase.request"].sudo().search(
[('origin', 'like', origin), ('rule_new_add', '=', True), ('state', '=', 'draft')])
if pr_ids:
pr_ids.write({'need_validation': False})
pr_ids.write({"state": "approved", 'need_validation': True, 'rule_new_add': False})
return res return res

View File

@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_purchase_request_wizard_group_user,purchase.request.wizard,model_purchase_request_wizard,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_purchase_request_wizard_group_user purchase.request.wizard model_purchase_request_wizard base.group_user 1 1 1 1

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="menu_purhcase_request" model="ir.ui.menu">
<field name="name">采购申请</field>
<field name="parent_id" ref="purchase.menu_purchase_root" />
<field name="sequence">2</field>
</record>
<record id="purchase_request.menu_purchase_request_pro_mgt" model="ir.ui.menu">
<field name="sequence">1</field>
<field name="parent_id" ref="jikimo_purchase_request.menu_purhcase_request"/>
</record>
<record id="purchase_request.menu_purchase_request_line" model="ir.ui.menu">
<field name="sequence">10</field>
<field name="parent_id" ref="jikimo_purchase_request.menu_purhcase_request"/>
</record>
</data>
</odoo>

View File

@@ -15,6 +15,26 @@
<field name="part_number"/> <field name="part_number"/>
<field name="part_name"/> <field name="part_name"/>
</xpath> </xpath>
<xpath expr="//button[@name='button_done']" position="attributes">
<attribute name="class"/>
</xpath>
<xpath expr="//button[@name='button_in_progress']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//button[@name='%(purchase_request.action_purchase_request_line_make_purchase_order)d']" position="attributes">
<attribute name="class">oe_highlight</attribute>
</xpath>
</field>
</record>
<record id="view_purchase_request_tree_sf" model="ir.ui.view">
<field name="name">purchase.request.sf.tree</field>
<field name="model">purchase.request</field>
<field name="inherit_id" ref="purchase_request.view_purchase_request_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='activity_ids']" position="attributes">
<attribute name="optional">hide</attribute>
</xpath>
</field> </field>
</record> </record>
@@ -63,4 +83,9 @@
</xpath> </xpath>
</field> </field>
</record> </record>
<record model="ir.actions.act_window" id="purchase_request.purchase_request_form_action">
<field name="name">Purchase Requests</field>
<field name="context"></field>
</record>
</odoo> </odoo>

View File

@@ -1,3 +1,4 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from . import purchase_request_line_make_purchase_order from . import purchase_request_line_make_purchase_order
from . import purchase_request_wizard

View File

@@ -0,0 +1,12 @@
from odoo import models, fields, api
class PurchaseRequestWizard(models.TransientModel):
_name = 'purchase.request.wizard'
_description = '采购申请向导'
purchase_request_id = fields.Many2one('purchase.request', string='采购申请')
message = fields.Char(string='提示', readonly=True)
def confirm(self):
return self.purchase_request_id.write({"state": "done"})

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.ui.view" id="purchase_request_wizard_wizard_form_view">
<field name="name">purchase.request.wizard.form.view</field>
<field name="model">purchase.request.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<div>
<div style="white-space: pre-wrap;">
<field name="message"/>
</div>
</div>
<footer>
<button string="确认" name="confirm" type="object" class="oe_highlight"/>
<button string="取消" class="btn btn-secondary" special="cancel"/>
</footer>
</sheet>
</form>
</field>
</record>
</odoo>

View File

@@ -1,10 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
{ {
'name': "机企猫 采购审批流程", 'name': "机企猫 采购申请审批流程",
'summary': """ 'summary': """
Short (1 phrase/line) summary of the module's purpose, used as 采购申请审批流程""",
subtitle on modules listing or apps.openerp.com""",
'description': """ 'description': """
Long description of module's purpose Long description of module's purpose

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import models from . import models
from . import stock_rule

View File

@@ -22,3 +22,9 @@ class PurchaseRequest(models.Model):
self.state = 'approved' self.state = 'approved'
return res return res
@api.model
def _get_under_validation_exceptions(self):
res = super(PurchaseRequest, self)._get_under_validation_exceptions()
res.append("state")
return res

View File

@@ -0,0 +1,16 @@
from odoo import models, api
class StockRule(models.Model):
_inherit = 'stock.rule'
def _run_buy(self, procurements):
res = super(StockRule, self)._run_buy(procurements)
# 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved
origins = list(set([procurement[0].origin for procurement in procurements]))
for origin in origins:
pr_ids = self.env["purchase.request"].sudo().search(
[('origin', 'like', origin), ('rule_new_add', '=', True), ('state', '=', 'draft')])
if pr_ids:
pr_ids.write({'need_validation': False})
pr_ids.write({"state": "approved", 'need_validation': True, 'rule_new_add': False})
return res

View File

@@ -1,12 +1,12 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
{ {
'name': "机企猫 采购申请审批流程", 'name': "机企猫 采购审批流程",
'summary': """ 'summary': """
采购申请审批流程""", 采购审批流程""",
'description': """ 'description': """
采购申请审批流程""", 采购审批流程""",
'author': "My Company", 'author': "My Company",
'website': "https://www.yourcompany.com", 'website': "https://www.yourcompany.com",

View File

@@ -4,6 +4,7 @@ import json
import logging import logging
from odoo.addons.sf_mrs_connect.controllers.controllers import Sf_Mrs_Connect from odoo.addons.sf_mrs_connect.controllers.controllers import Sf_Mrs_Connect
from odoo.addons.sf_manufacturing.controllers.controllers import Manufacturing_Connect from odoo.addons.sf_manufacturing.controllers.controllers import Manufacturing_Connect
from odoo.addons.sf_base.decorators.api_log import api_log
from datetime import datetime from datetime import datetime
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -12,6 +13,7 @@ class WorkorderExceptionConroller(http.Controller):
@http.route('/AutoDeviceApi/BillError', type='json', auth='public', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/BillError', type='json', auth='public', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('工单对接错误', requester='中控系统')
def workder_exception(self, **kw): def workder_exception(self, **kw):
""" """
记录工单异常 记录工单异常

View File

@@ -9,7 +9,7 @@ class MrpWorkorder(models.Model):
exception_ids = fields.One2many('jikimo.workorder.exception', 'workorder_id', string='工单异常记录') exception_ids = fields.One2many('jikimo.workorder.exception', 'workorder_id', string='工单异常记录')
def write(self, values): def write(self, values):
if values.get('test_results') and self.exception_ids: if 'test_results' in values and self.exception_ids:
pending_exception = self.exception_ids.filtered( pending_exception = self.exception_ids.filtered(
lambda exc: exc.state == 'pending' and exc.exception_code == 'YC0005' lambda exc: exc.state == 'pending' and exc.exception_code == 'YC0005'
) )

View File

@@ -133,6 +133,7 @@ class QualityCheck(models.Model):
part_name = fields.Char('零件名称', related='product_id.part_name', readonly=False, store=True) part_name = fields.Char('零件名称', related='product_id.part_name', readonly=False, store=True)
part_number = fields.Char('零件图号', related='product_id.part_number', readonly=False, store=True) part_number = fields.Char('零件图号', related='product_id.part_number', readonly=False, store=True)
material_name = fields.Char('材料名称', compute='_compute_material_name') material_name = fields.Char('材料名称', compute='_compute_material_name')
model_id = fields.Char('模型ID', related='product_id.model_id')
# # 总数量值为调拨单_产品明细_数量 # # 总数量值为调拨单_产品明细_数量
# total_qty = fields.Float('总数量', compute='_compute_total_qty', readonly=True) # total_qty = fields.Float('总数量', compute='_compute_total_qty', readonly=True)
@@ -338,7 +339,7 @@ class QualityCheck(models.Model):
# 4. 获取报告动作并生成PDF此时二维码将包含正确的文档ID # 4. 获取报告动作并生成PDF此时二维码将包含正确的文档ID
report_action = self.env.ref('sf_quality.action_report_quality_inspection') report_action = self.env.ref('sf_quality.action_report_quality_inspection')
pdf_content, _ = report_action._render_qweb_pdf( pdf_content, v = report_action._render_qweb_pdf(
report_ref=report_action.report_name, report_ref=report_action.report_name,
res_ids=self.ids res_ids=self.ids
) )

View File

@@ -493,6 +493,9 @@
<field name="picking_id"/> <field name="picking_id"/>
<field name="lot_id"/> <field name="lot_id"/>
<field name="team_id"/> <field name="team_id"/>
<field name="part_number"/>
<field name="part_name"/>
<field name="model_id"/>
<filter string="In Progress" name="progress" domain="[('quality_state', '=', 'none')]"/> <filter string="In Progress" name="progress" domain="[('quality_state', '=', 'none')]"/>
<filter string="Passed" name="passed" domain="[('quality_state', '=', 'pass')]"/> <filter string="Passed" name="passed" domain="[('quality_state', '=', 'pass')]"/>
<filter string="Failed" name="failed" domain="[('quality_state', '=', 'fail')]"/> <filter string="Failed" name="failed" domain="[('quality_state', '=', 'fail')]"/>

View File

@@ -103,12 +103,19 @@ class PrintingUtils(models.AbstractModel):
self.send_to_printer(host, port, zpl_code) self.send_to_printer(host, port, zpl_code)
def add_qr_code_to_pdf(self, pdf_path:str, content:str, buttom_text:Optional[str]=False): def add_qr_code_to_pdf(
self,
pdf_path:str,
content:str,
qr_code_buttom_text:Optional[str]=False,
buttom_text:Optional[str]=False,
):
""" """
在PDF文件中添加二维码 在PDF文件中添加二维码
:param pdf_path: PDF文件路径 :param pdf_path: PDF文件路径
:param content: 二维码内容 :param content: 二维码内容
:param buttom_text: 二维码下方文字 :param qr_code_buttom_text: 二维码下方文字
:param buttom_text: 正文下方文字
:return: 是否成功 :return: 是否成功
""" """
if not os.path.exists(pdf_path): if not os.path.exists(pdf_path):
@@ -156,8 +163,9 @@ class PrintingUtils(models.AbstractModel):
existing_pdf = PdfFileReader(original_file) existing_pdf = PdfFileReader(original_file)
output = PdfFileWriter() output = PdfFileWriter()
# 处理一页 # 处理最后一页
page = existing_pdf.getPage(0) last_page = existing_pdf.getNumPages() - 1
page = existing_pdf.getPage(last_page)
# 获取页面尺寸 # 获取页面尺寸
page_width = float(page.mediaBox.getWidth()) page_width = float(page.mediaBox.getWidth())
page_height = float(page.mediaBox.getHeight()) page_height = float(page.mediaBox.getHeight())
@@ -179,13 +187,29 @@ class PrintingUtils(models.AbstractModel):
qr_y = margin + 20 # 将二维码向上移动一点,为文字留出空间 qr_y = margin + 20 # 将二维码向上移动一点,为文字留出空间
c.drawImage(qr_temp_path, page_width - qr_size - margin, qr_y, width=qr_size, height=qr_size) c.drawImage(qr_temp_path, page_width - qr_size - margin, qr_y, width=qr_size, height=qr_size)
if buttom_text: if qr_code_buttom_text:
# 在二维码下方绘制文字 # 在二维码下方绘制文字
text = buttom_text text = qr_code_buttom_text
text_width = c.stringWidth(text, "SimSun" if font_found else "Helvetica", 10) # 准确计算文字宽度 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_x = page_width - qr_size - margin + (qr_size - text_width) / 2 # 文字居中对齐
text_y = margin + 20 # 文字位置靠近底部 text_y = margin + 20 # 文字位置靠近底部
c.drawString(text_x, text_y, text) c.drawString(text_x, text_y, text)
# 设置字体
if font_found:
c.setFont('SimSun', 12) # 增大字体大小到14pt
else:
# 如果没有找到中文字体,使用默认字体
c.setFont('Helvetica', 120)
logging.warning("未找到中文字体,将使用默认字体")
if buttom_text:
# 在下方中间添加文字
text = buttom_text
text_width = c.stringWidth(text, "SimSun" if font_found else "Helvetica", 12) # 准确计算文字宽度
text_x = (page_width - text_width) / 2 # 文字居中对齐
text_y = margin + 20 # 文字位置靠近底部
c.drawString(text_x, text_y, text)
c.save() c.save()
@@ -196,11 +220,12 @@ class PrintingUtils(models.AbstractModel):
# 合并原始页面和二维码页面 # 合并原始页面和二维码页面
page.mergePage(qr_page) page.mergePage(qr_page)
output.addPage(page)
# 添加剩余的页面 # 添加剩余的页面
for i in range(1, existing_pdf.getNumPages()): for i in range(0, last_page):
output.addPage(existing_pdf.getPage(i)) output.addPage(existing_pdf.getPage(i))
output.addPage(page)
# 保存最终的PDF到一个临时文件 # 保存最终的PDF到一个临时文件
final_temp_path = pdf_path + '.tmp' final_temp_path = pdf_path + '.tmp'

View File

@@ -34,17 +34,20 @@ def api_log(name=None, requester=None):
# 计算响应时间 # 计算响应时间
end_time = datetime.now() end_time = datetime.now()
response_time = (end_time - start_time).total_seconds() response_time = (end_time - start_time).total_seconds()
# 获取响应状态
status = result.get('code') if 'code' in result else result.get('ErrorCode') if 'ErrorCode' in result else 200
# 创建日志记录 # 创建日志记录
log_vals = { log_vals = {
'name': name or func.__name__, 'name': name or func.__name__,
'path': path, 'path': path,
'method': method, 'method': method.upper(),
'request_data': json.dumps(request_data, ensure_ascii=False), 'request_data': json.dumps(request_data, ensure_ascii=False),
'response_data': json.dumps(result, ensure_ascii=False), 'response_data': json.dumps(result, ensure_ascii=False),
'remote_addr': remote_addr, 'remote_addr': remote_addr,
'response_time': response_time, 'response_time': response_time,
'status': result.get('code') or result.get('ErrorCode') or 500, 'status': 200 if status == 0 else status,
'requester': requester, 'requester': requester,
'responser': '智能工厂' 'responser': '智能工厂'
} }

View File

@@ -59,7 +59,7 @@ class ApiRequestLog(models.Model):
self.sudo().create({ self.sudo().create({
'name': name, 'name': name,
'path': url, 'path': url,
'method': method, 'method': method.upper(),
'request_data': request_body, 'request_data': request_body,
'response_data': response_body, 'response_data': response_body,
'remote_addr': None, 'remote_addr': None,

View File

@@ -5,13 +5,15 @@
<field name="model">api.request.log</field> <field name="model">api.request.log</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree> <tree>
<field name="create_date"/>
<field name="name"/> <field name="name"/>
<field name="path"/> <field name="path"/>
<field name="method"/> <field name="method"/>
<field name="remote_addr"/> <field name="remote_addr"/>
<field name="response_time"/> <field name="response_time" sum="0"/>
<field name="status"/> <field name="requester"/>
<field name="responser"/>
<field name="create_date" string="请求时间"/>
<field name="status" sum="0"/>
</tree> </tree>
</field> </field>
</record> </record>
@@ -32,6 +34,8 @@
<group> <group>
<field name="response_time"/> <field name="response_time"/>
<field name="status"/> <field name="status"/>
<field name="requester"/>
<field name="responser"/>
<field name="create_date" string="请求时间"/> <field name="create_date" string="请求时间"/>
</group> </group>
</group> </group>
@@ -48,6 +52,23 @@
</field> </field>
</record> </record>
<record model="ir.ui.view" id="view_api_request_log_search">
<field name="name">api.request.log.search</field>
<field name="model">api.request.log</field>
<field name="arch" type="xml">
<search string="API请求日志">
<field name="name"/>
<field name="requester"/>
<field name="responser"/>
<group>
<filter name="name" context="{'group_by':'name'}"/>
<filter name="requester" context="{'group_by':'requester'}"/>
<filter name="responser" context="{'group_by':'responser'}"/>
</group>
</search>
</field>
</record>
<record id="action_api_request_log" model="ir.actions.act_window"> <record id="action_api_request_log" model="ir.actions.act_window">
<field name="name">API请求日志</field> <field name="name">API请求日志</field>
<field name="res_model">api.request.log</field> <field name="res_model">api.request.log</field>

View File

@@ -17,7 +17,7 @@ class ResProductCategory(models.Model):
class ResProductProduct(models.Model): class ResProductProduct(models.Model):
_inherit = 'product.product' _inherit = 'product.product'
single_manufacturing = fields.Boolean(string="单个制造") # single_manufacturing = fields.Boolean(string="单个制造")
is_bfm = fields.Boolean('业务平台是否自动创建', default=False) is_bfm = fields.Boolean('业务平台是否自动创建', default=False)

View File

@@ -1,16 +1,16 @@
import logging # import logging
from odoo import fields, models, api # from odoo import fields, models, api
from odoo.exceptions import UserError # from odoo.exceptions import UserError
from odoo.tools import str2bool # from odoo.tools import str2bool
class ResMrpRoutingWorkcenter(models.Model): # class ResMrpRoutingWorkcenter(models.Model):
_inherit = 'mrp.routing.workcenter' # _inherit = 'mrp.routing.workcenter'
def init(self): # def init(self):
super(ResMrpRoutingWorkcenter, self).init() # super(ResMrpRoutingWorkcenter, self).init()
# 在模块初始化时触发计算字段的更新 # # 在模块初始化时触发计算字段的更新
records = self.search([]) # records = self.search([])
if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_workcenter',default='False')): # if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_workcenter',default='False')):
return # return
records.optional_process_parameters_date() # records.optional_process_parameters_date()
self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_workcenter', True) # self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_workcenter', True)

View File

@@ -1,87 +1,85 @@
# -*- coding: utf-8 -*- # # -*- coding: utf-8 -*-
import logging # import logging
from odoo import fields, models, api # from odoo import fields, models, api
from odoo.exceptions import UserError # from odoo.exceptions import UserError
from odoo.tools import str2bool # from odoo.tools import str2bool
class SfProductionProcessParameter(models.Model): # class SfProductionProcessParameter(models.Model):
_inherit = 'sf.production.process.parameter' # _inherit = 'sf.production.process.parameter'
@api.model # @api.model
def create(self, vals): # def create(self, vals):
# if vals.get('code', '/') == '/' or vals.get('code', '/') is False: # # if vals.get('code', '/') == '/' or vals.get('code', '/') is False:
# vals['code'] = '101'+self.routing_id.code +self.env['ir.sequence'].next_by_code('sf.production.process.parameter') # # vals['code'] = '101'+self.routing_id.code +self.env['ir.sequence'].next_by_code('sf.production.process.parameter')
if vals.get('routing_id'): # if vals.get('routing_id'):
# vals['gain_way'] = '外协' # # vals['gain_way'] = '外协'
routing_id = self.env['mrp.routing.workcenter'].browse(vals.get('routing_id')) # routing_id = self.env['mrp.routing.workcenter'].browse(vals.get('routing_id'))
if routing_id.surface_technics_id and not vals.get('process_id'): # if routing_id.surface_technics_id and not vals.get('process_id'):
vals['process_id'] = routing_id.surface_technics_id.id # vals['process_id'] = routing_id.surface_technics_id.id
if vals.get('code', '/') == '/' or vals.get('code', '/') is False: # if vals.get('code', '/') == '/' or vals.get('code', '/') is False:
vals['code'] = '101' + routing_id.code + self.env['ir.sequence'].next_by_code( # vals['code'] = '101' + routing_id.code + self.env['ir.sequence'].next_by_code(
'sf.production.process.parameter') # 'sf.production.process.parameter')
obj = super(SfProductionProcessParameter, self).create(vals) # obj = super(SfProductionProcessParameter, self).create(vals)
return obj # return obj
def create_service_product(self): # def create_service_product(self):
if not self.active: # service_categ = self.env.ref(
return # 'sf_dlm.product_category_surface_technics_sf').sudo()
service_categ = self.env.ref(
'sf_dlm.product_category_surface_technics_sf').sudo()
product_name = f"{self.process_id.name}_{self.name}" # product_name = f"{self.process_id.name}_{self.name}"
product_id = self.env['product.template'].search( # product_id = self.env['product.template'].search(
[("name", '=', product_name)]) # [("name", '=', product_name)])
if product_id: # if product_id:
product_id.server_product_process_parameters_id = self.id # product_id.server_product_process_parameters_id = self.id
else: # else:
res_partner = self.env['res.partner'].search([('name','=','湖南傲派自动化设备有限公司')]) # res_partner = self.env['res.partner'].search([('name','=','湖南傲派自动化设备有限公司')])
self.env['product.template'].create({ # self.env['product.template'].create({
'detailed_type': 'service', # 'detailed_type': 'service',
'name': product_name, # 'name': product_name,
'invoice_policy': 'delivery', # 'invoice_policy': 'delivery',
'categ_id': service_categ.id, # 'categ_id': service_categ.id,
'description': f"基于{self.name}创建的服务产品", # 'description': f"基于{self.name}创建的服务产品",
'sale_ok': True, # 可销售 # 'sale_ok': True, # 可销售
'purchase_ok': True, # 可采购 # 'purchase_ok': True, # 可采购
'server_product_process_parameters_id': self.id, # 'server_product_process_parameters_id': self.id,
'seller_ids': [(0, 0, { # 'seller_ids': [(0, 0, {
# 'delay': 1, # # 'delay': 1,
'partner_id': res_partner.id, # 'partner_id': res_partner.id,
'price': 1, })], # 'price': 1, })],
}) # })
def create_work_center(self): # def create_work_center(self):
production_process_parameter = self # production_process_parameter = self
if not production_process_parameter.process_id: # if not production_process_parameter.process_id:
return # return
if not production_process_parameter.routing_id: # if not production_process_parameter.routing_id:
workcenter_id = self.env['mrp.routing.workcenter'].search( # workcenter_id = self.env['mrp.routing.workcenter'].search(
[("surface_technics_id", '=', production_process_parameter.process_id.id)]) # [("surface_technics_id", '=', production_process_parameter.process_id.id)])
if not workcenter_id: # if not workcenter_id:
outsourcing_work_center = self.env['mrp.workcenter'].search( # outsourcing_work_center = self.env['mrp.workcenter'].search(
[("name", '=', '外协工作中心')]) # [("name", '=', '外协工作中心')])
routing_id = self.env['mrp.routing.workcenter'].create({ # routing_id = self.env['mrp.routing.workcenter'].create({
'workcenter_ids': [(6, 0, outsourcing_work_center.ids)], # 'workcenter_ids': [(6, 0, outsourcing_work_center.ids)],
'routing_tag': 'special', # 'routing_tag': 'special',
'routing_type': '表面工艺', # 'routing_type': '表面工艺',
'is_outsource': True, # 'is_outsource': True,
'surface_technics_id': production_process_parameter.process_id.id, # 'surface_technics_id': production_process_parameter.process_id.id,
'name': production_process_parameter.process_id.name, # 'name': production_process_parameter.process_id.name,
}) # })
production_process_parameter.routing_id = routing_id.id # production_process_parameter.routing_id = routing_id.id
else: # else:
production_process_parameter.routing_id = workcenter_id.id # production_process_parameter.routing_id = workcenter_id.id
def init(self): # def init(self):
super(SfProductionProcessParameter, self).init() # super(SfProductionProcessParameter, self).init()
# 在模块初始化时触发计算字段的更新 # # 在模块初始化时触发计算字段的更新
records = self.search([]) # records = self.search([])
if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_process', # if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_process',
default='False')): # default='False')):
return # return
for record in records: # for record in records:
if not record.outsourced_service_products: # if not record.outsourced_service_products:
record.create_service_product() # record.create_service_product()
record.create_work_center() # record.create_work_center()
self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_process', True) # self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_process', True)

View File

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

View File

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

View File

@@ -56,6 +56,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/GetShiftPlan', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/GetShiftPlan', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('获取日计划', requester='中控系统')
def get_ShiftPlan(self, **kw): def get_ShiftPlan(self, **kw):
""" """
自动化每天获取机台日计划 自动化每天获取机台日计划

View File

@@ -45,6 +45,8 @@ class JikimoSaleRoutePicking(Sf_Bf_Connect):
product.product_tmpl_id.is_customer_provided = True if item['embryo_redundancy_id'] else False product.product_tmpl_id.is_customer_provided = True if item['embryo_redundancy_id'] else False
order_id.with_user(request.env.ref("base.user_admin")).sale_order_create_line(product, item) order_id.with_user(request.env.ref("base.user_admin")).sale_order_create_line(product, item)
i += 1 i += 1
if kw.get('contract_file_name') and kw.get('contract_file'):
order_id.create_sale_documents(kw.get('contract_file_name'), kw.get('contract_file'))
res['factory_order_no'] = order_id.name res['factory_order_no'] = order_id.name
order_id.confirm_to_supply_method() order_id.confirm_to_supply_method()
except Exception as e: except Exception as e:

View File

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

View File

@@ -279,7 +279,7 @@ class MrpProduction(models.Model):
production_id.part_name = production_id.product_id.part_name production_id.part_name = production_id.product_id.part_name
elif production_id.product_id.categ_id.type == '坯料': elif production_id.product_id.categ_id.type == '坯料':
product_name = '' product_name = ''
match = re.search(r'(S\d{5}-\d)', production_id.product_id.name) match = re.search(r'(S\d{5}-\d+)', production_id.product_id.name)
# 如果匹配成功,提取结果 # 如果匹配成功,提取结果
if match: if match:
product_name = match.group(0) product_name = match.group(0)
@@ -636,13 +636,17 @@ class MrpProduction(models.Model):
# 增加触发时间参数 # 增加触发时间参数
def update_programming_state(self, trigger_time=None, reprogramming_reason=None): def update_programming_state(self, trigger_time=None, reprogramming_reason=None):
try: try:
reason = ""
manufacturing_type = None manufacturing_type = None
if self.is_scrap: if self.is_scrap:
manufacturing_type = 'scrap' manufacturing_type = 'scrap'
reason = "报废"
elif self.tool_state == '2': elif self.tool_state == '2':
manufacturing_type = 'invalid_tool_rework' manufacturing_type = 'invalid_tool_rework'
reason = "无效功能刀具"
elif self.is_rework: elif self.is_rework:
manufacturing_type = 'rework' manufacturing_type = 'rework'
reason = "返工"
res = {'programming_no': self.programming_no, res = {'programming_no': self.programming_no,
'manufacturing_type': manufacturing_type, 'manufacturing_type': manufacturing_type,
'trigger_time': trigger_time, 'trigger_time': trigger_time,
@@ -657,6 +661,16 @@ class MrpProduction(models.Model):
result = json.loads(ret['result']) result = json.loads(ret['result'])
logging.info('update_programming_state-ret:%s' % result) logging.info('update_programming_state-ret:%s' % result)
if result['status'] == 1: if result['status'] == 1:
self.programming_record_ids.create({
'number': len(self.programming_record_ids) + 1,
'production_id': self.id,
'reason': reason,
'programming_method': False,
'current_programming_count': False,
'target_production_id': False,
'apply_time': fields.Datetime.now(),
'send_time': False,
})
self.write({'is_rework': True}) self.write({'is_rework': True})
else: else:
raise UserError(ret['message']) raise UserError(ret['message'])
@@ -787,6 +801,17 @@ class MrpProduction(models.Model):
if ret['status'] == 1: if ret['status'] == 1:
self.write( self.write(
{'programming_no': ret['programming_no'], 'programming_state': '编程中', 'work_state': '编程中'}) {'programming_no': ret['programming_no'], 'programming_state': '编程中', 'work_state': '编程中'})
# 生成编程记录
self.programming_record_ids.create({
'number': len(self.programming_record_ids) + 1,
'production_id': self.id,
'reason': '首次下发',
'programming_method': False,
'current_programming_count': False,
'target_production_id': False,
'apply_time': fields.Datetime.now(),
'send_time': False,
})
else: else:
raise UserError(ret['message']) raise UserError(ret['message'])
except Exception as e: except Exception as e:
@@ -900,43 +925,41 @@ class MrpProduction(models.Model):
for workorder in production.workorder_ids: for workorder in production.workorder_ids:
workorder.duration_expected = workorder._get_duration_expected() workorder.duration_expected = workorder._get_duration_expected()
def _create_subcontract_purchase_request(self, purchase_request_line): # def _create_subcontract_purchase_request(self, purchase_request_line):
sorted_list = sorted(purchase_request_line, key=itemgetter('name')) # sorted_list = sorted(purchase_request_line, key=itemgetter('name'))
grouped_purchase_request_line = { # grouped_purchase_request_line = {
k: list(g) # k: list(g)
for k, g in groupby(sorted_list, key=itemgetter('name')) # for k, g in groupby(sorted_list, key=itemgetter('name'))
} # }
for name, request_line in grouped_purchase_request_line.items(): # for name, request_line in grouped_purchase_request_line.items():
request_line_sorted_list = sorted(request_line, key=itemgetter('product_id')) # request_line_sorted_list = sorted(request_line, key=itemgetter('product_id'))
grouped_purchase_request_line_sorted_list = { # grouped_purchase_request_line_sorted_list = {
k: list(g) # k: list(g)
for k, g in groupby(request_line_sorted_list, key=itemgetter('product_id')) # for k, g in groupby(request_line_sorted_list, key=itemgetter('product_id'))
} # }
purchase_request_model = self.env["purchase.request"] # purchase_request_model = self.env["purchase.request"]
origin = ", ".join({item['production_name'] for item in request_line_sorted_list if item.get('production_name')}) # origin = ", ".join({item['production_name'] for item in request_line_sorted_list if item.get('production_name')})
pr = purchase_request_model.create({ # pr = purchase_request_model.create({
"origin": origin, # "origin": origin,
"company_id": self.company_id.id, # "company_id": self.company_id.id,
"picking_type_id": self.env.ref('stock.picking_type_in').id, # "picking_type_id": self.env.ref('stock.picking_type_in').id,
"group_id": request_line[0].get('group_id'), # "group_id": request_line[0].get('group_id'),
"requested_by": self.env.context.get("uid", self.env.uid), # "requested_by": self.env.context.get("uid", self.env.uid),
"assigned_to": False, # "assigned_to": False,
"bom_id": self[0].bom_id.id, # "bom_id": self[0].bom_id.id,
"is_subcontract":True, # "is_subcontract":True,
}) # })
self[0].bom_id.bom_line_ids.product_id.route_ids = [(4,self.env.ref( # self[0].bom_id.bom_line_ids.product_id.route_ids = [(4,self.env.ref(
'sf_stock.stock_route_process_outsourcing').id)] # 'sf_stock.stock_route_process_outsourcing').id)]
for product_id, request_line_list in grouped_purchase_request_line_sorted_list.items(): # for product_id, request_line_list in grouped_purchase_request_line_sorted_list.items():
cur_request_line = request_line_list[0] # cur_request_line = request_line_list[0]
if cur_request_line['product_qty'] == 1: # # cur_request_line['product_qty'] = cur_request_line['product_qty']
cur_request_line['product_qty'] = len(request_line_list) # cur_request_line['request_id'] = pr.id
# cur_request_line['product_qty'] = cur_request_line['product_qty'] # cur_request_line['origin'] = ", ".join({item['production_name'] for item in request_line_list if item.get('production_name')})
cur_request_line['request_id'] = pr.id # cur_request_line.pop('group_id', None)
cur_request_line['origin'] = ", ".join({item['production_name'] for item in request_line_list if item.get('production_name')}) # cur_request_line.pop('production_name', None)
cur_request_line.pop('group_id', None) # self.env["purchase.request.line"].create(cur_request_line)
cur_request_line.pop('production_name', None) # pr.button_approved()
self.env["purchase.request.line"].create(cur_request_line)
pr.button_approved()
# 外协出入库单处理 # 外协出入库单处理
def get_subcontract_pick_purchase(self): def get_subcontract_pick_purchase(self):
@@ -964,14 +987,14 @@ class MrpProduction(models.Model):
if not sorted_workorders: if not sorted_workorders:
return return
for workorders in reversed(sorted_workorders): 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) 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( # purchase_request_line = purchase_request_line + self.env['purchase.order'].get_purchase_request(
workorders, production) # workorders, production)
all_workorders += workorders # all_workorders += workorders
self._create_subcontract_purchase_request(purchase_request_line) # self._create_subcontract_purchase_request(purchase_request_line)
for workorder in all_workorders: # for workorder in all_workorders:
workorder._compute_pr_mp_count() # workorder._compute_pr_mp_count()
# 工单排序 # 工单排序
def _reset_work_order_sequence1(self, k): def _reset_work_order_sequence1(self, k):
for rec in self: for rec in self:
@@ -1774,7 +1797,6 @@ class MrpProduction(models.Model):
""" """
检查前置条件:制造订单【状态】=“待排程、待加工”,制造订单的【编程状态】=“已编程”。 检查前置条件:制造订单【状态】=“待排程、待加工”,制造订单的【编程状态】=“已编程”。
""" """
print('申请编程')
if len(self) > 1: if len(self) > 1:
raise UserError('仅支持选择单个制造订单进行编程申请,请重新选择') raise UserError('仅支持选择单个制造订单进行编程申请,请重新选择')
for production in self: for production in self:
@@ -1843,6 +1865,7 @@ class sf_programming_record(models.Model):
target_production_id = fields.Char('目标制造单号') target_production_id = fields.Char('目标制造单号')
apply_time = fields.Datetime('申请时间') apply_time = fields.Datetime('申请时间')
send_time = fields.Datetime('下发时间') send_time = fields.Datetime('下发时间')
apply_uid = fields.Many2one('res.users', '申请人', default=lambda self: self.env.user)
class sf_detection_result(models.Model): class sf_detection_result(models.Model):

View File

@@ -1,7 +1,7 @@
import logging import logging
from odoo import fields, models, api from odoo import fields, models, api
from odoo.exceptions import UserError from odoo.exceptions import UserError
from odoo.tools import str2bool # from odoo.tools import str2bool
class ResMrpRoutingWorkcenter(models.Model): class ResMrpRoutingWorkcenter(models.Model):
@@ -25,20 +25,20 @@ class ResMrpRoutingWorkcenter(models.Model):
workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_route', required=True) workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_route', required=True)
bom_id = fields.Many2one('mrp.bom', required=False) bom_id = fields.Many2one('mrp.bom', required=False)
surface_technics_id = fields.Many2one('sf.production.process', string="表面工艺") surface_technics_id = fields.Many2one('sf.production.process', string="表面工艺")
optional_process_parameters = fields.One2many('sf.production.process.parameter','routing_id',string='可选工艺参数') # optional_process_parameters = fields.One2many('sf.production.process.parameter','routing_id',string='可选工艺参数')
reserved_duration = fields.Float('预留时长', default=30, tracking=True) reserved_duration = fields.Float('预留时长', default=30, tracking=True)
is_outsource = fields.Boolean('外协', default=False) is_outsource = fields.Boolean('外协', default=False)
individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录') individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录')
@api.onchange('surface_technics_id') # @api.onchange('surface_technics_id')
def optional_process_parameters_date(self): # def optional_process_parameters_date(self):
for record in self: # for record in self:
if not record.surface_technics_id: # if not record.surface_technics_id:
continue # continue
parameter_ids = self.env['sf.production.process.parameter'].search([ # parameter_ids = self.env['sf.production.process.parameter'].search([
('process_id', '=', record.surface_technics_id.id), # ('process_id', '=', record.surface_technics_id.id),
]) # ])
record.optional_process_parameters = parameter_ids.ids # record.optional_process_parameters = parameter_ids.ids
# @api.model # @api.model
# def _auto_init(self): # def _auto_init(self):

View File

@@ -21,16 +21,16 @@ class ResWorkcenter(models.Model):
related='equipment_id.production_line_id', store=True) related='equipment_id.production_line_id', store=True)
is_process_outsourcing = fields.Boolean('工艺外协') is_process_outsourcing = fields.Boolean('工艺外协')
users_ids = fields.Many2many("res.users", 'users_workcenter', tracking=True) users_ids = fields.Many2many("res.users", 'users_workcenter', tracking=True)
@api.constrains('name') # @api.constrains('name')
def _check_unique_name_code(self): # def _check_unique_name_code(self):
for record in self: # for record in self:
# 检查是否已经存在相同的 name 和 code 组合 # # 检查是否已经存在相同的 name 和 code 组合
existing = self.search([ # existing = self.search([
('name', '=', record.name), # ('name', '=', record.name),
('id', '!=', record.id) # 排除当前记录 # ('id', '!=', record.id) # 排除当前记录
]) # ])
if existing: # if existing:
raise ValueError('记录已存在') # raise ValueError('记录已存在')
def write(self, vals): def write(self, vals):
if 'users_ids' in vals: if 'users_ids' in vals:
old_users = self.users_ids old_users = self.users_ids

View File

@@ -70,21 +70,21 @@ class ResMrpWorkOrder(models.Model):
delivery_warning = fields.Selection([('normal', '正常'), ('warning', '告警'), ('overdue', '逾期')], string='时效', delivery_warning = fields.Selection([('normal', '正常'), ('warning', '告警'), ('overdue', '逾期')], string='时效',
tracking=True) tracking=True)
back_button_display = fields.Boolean(default=False, compute='_compute_back_button_display', store=True) back_button_display = fields.Boolean(default=False, compute='_compute_back_button_display', store=True)
pr_mp_count = fields.Integer('采购申请单数量', compute='_compute_pr_mp_count', store=True) # pr_mp_count = fields.Integer('采购申请单数量', compute='_compute_pr_mp_count', store=True)
@api.depends('state') # @api.depends('state')
def _compute_pr_mp_count(self): # def _compute_pr_mp_count(self):
for item in self: # for item in self:
if not item.is_subcontract: # if not item.is_subcontract:
item.pr_mp_count = 0 # item.pr_mp_count = 0
continue # continue
pr_ids = self.env['purchase.request'].sudo().search( # pr_ids = self.env['purchase.request'].sudo().search(
[('origin', 'like', item.production_id.name), ('is_subcontract', '=', 'True'), # [('origin', 'like', item.production_id.name), ('is_subcontract', '=', 'True'),
('state', '!=', 'rejected')]) # ('state', '!=', 'rejected')])
if pr_ids: # if pr_ids:
item.pr_mp_count = len(pr_ids) # item.pr_mp_count = len(pr_ids)
else: # else:
item.pr_mp_count = 0 # item.pr_mp_count = 0
@api.depends('state') @api.depends('state')
def _compute_back_button_display(self): def _compute_back_button_display(self):
@@ -130,8 +130,14 @@ class ResMrpWorkOrder(models.Model):
record.back_button_display = False record.back_button_display = False
else: else:
next_workorder = sorted_workorders[position + 1] next_workorder = sorted_workorders[position + 1]
next_state = next_workorder.state # 持续获取下一个工单,直到找到一个不是返工的工单
if (next_state == 'ready' or ( while next_workorder and next_workorder.state == 'rework':
position += 1
if position + 1 < len(sorted_workorders):
next_workorder = sorted_workorders[position + 1]
else:
next_workorder = None
if next_workorder and (next_workorder.state == 'ready' or (
next_workorder.state == 'waiting' and next_workorder.is_subcontract)) and cur_workorder.state == 'done': next_workorder.state == 'waiting' and next_workorder.is_subcontract)) and cur_workorder.state == 'done':
record.back_button_display = True record.back_button_display = True
else: else:
@@ -440,41 +446,51 @@ class ResMrpWorkOrder(models.Model):
action['context'] = dict(self._context, default_origin=self.name) action['context'] = dict(self._context, default_origin=self.name)
return action return action
@api.depends('state', 'production_id.name')
def _compute_surface_technics_purchase_ids(self): def _compute_surface_technics_purchase_ids(self):
for order in self: for order in self:
pr_ids = self.env['purchase.request'].sudo().search( if order.routing_type == '表面工艺' and order.state not in ['cancel']:
[('origin', 'like', self.production_id.name), ('is_subcontract', '=', 'True'), domain = [('purchase_type', '=', 'consignment'),
('state', '!=', 'rejected')]) ('origin', 'like', '%' + self.production_id.name + '%'),
if pr_ids.purchase_count: ('state', '!=', 'cancel')]
order.surface_technics_purchase_count = pr_ids.purchase_count # 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
else: else:
order.surface_technics_purchase_count = 0 order.surface_technics_purchase_count = 0
def action_view_pr_mrp_workorder(self): # def action_view_pr_mrp_workorder(self):
""" # """
采购请求 # 采购请求
""" # """
self.ensure_one() # self.ensure_one()
pr_ids = self.env['purchase.request'].sudo().search( # pr_ids = self.env['purchase.request'].sudo().search(
[('origin', 'like', self.production_id.name), ('is_subcontract', '=', 'True'), # [('origin', 'like', self.production_id.name), ('is_subcontract', '=', 'True'),
('state', '!=', 'rejected')]) # ('state', '!=', 'rejected')])
action = { # action = {
'res_model': 'purchase.request', # 'res_model': 'purchase.request',
'type': 'ir.actions.act_window', # 'type': 'ir.actions.act_window',
} # }
if len(pr_ids) == 1: # if len(pr_ids) == 1:
action.update({ # action.update({
'view_mode': 'form', # 'view_mode': 'form',
'res_id': pr_ids[0].id, # 'res_id': pr_ids[0].id,
}) # })
else: # else:
action.update({ # action.update({
'name': _("%s生成采购请求单", self.name), # 'name': _("从 %s生成采购请求单", self.name),
'domain': [('id', 'in', pr_ids)], # 'domain': [('id', 'in', pr_ids)],
'view_mode': 'tree,form', # 'view_mode': 'tree,form',
}) # })
return action # return action
def action_view_surface_technics_purchase(self): def action_view_surface_technics_purchase(self):
self.ensure_one() self.ensure_one()
@@ -492,19 +508,16 @@ class ResMrpWorkOrder(models.Model):
# if technology_design.is_auto is False: # if technology_design.is_auto is False:
# domain = [('origin', '=', self.production_id.name)] # domain = [('origin', '=', self.production_id.name)]
# else: # else:
pr_ids = self.env['purchase.request'].sudo().search( purchase_orders_id = self._get_surface_technics_purchase_ids()
[('origin', 'like', self.production_id.name), ('is_subcontract', '=', 'True'), result = {
('state', '!=', 'rejected')]) "type": "ir.actions.act_window",
# purchase_orders_id = self._get_surface_technics_purchase_ids() "res_model": "purchase.order",
# result = { "res_id": purchase_orders_id.id,
# "type": "ir.actions.act_window", # "domain": [['id', 'in', self.purchase_id]],
# "res_model": "purchase.order", "name": _("Purchase Orders"),
# "res_id": purchase_orders_id.id, 'view_mode': 'form',
# # "domain": [['id', 'in', self.purchase_id]], }
# "name": _("Purchase Orders"), return result
# 'view_mode': 'form',
# }
return pr_ids.action_view_purchase_order()
def _get_surface_technics_purchase_ids(self): 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'),
@@ -731,21 +744,25 @@ class ResMrpWorkOrder(models.Model):
# self.workpiece_delivery_ids[0].write({'rfid_code': self.rfid_code}) # self.workpiece_delivery_ids[0].write({'rfid_code': self.rfid_code})
def get_plan_workorder(self, production_line): def get_plan_workorder(self, production_line):
tomorrow = (date.today() + timedelta(days=+1)).strftime("%Y-%m-%d") tomorrow = (date.today() + timedelta(days=1)).strftime("%Y-%m-%d")
tomorrow_start = tomorrow + ' 00:00:00' tomorrow_start = f"{tomorrow} 00:00:00"
tomorrow_end = tomorrow + ' 23:59:59' tomorrow_end = f"{tomorrow} 23:59:59"
logging.info('tomorrow:%s' % tomorrow) logging.info('tomorrow:%s' % tomorrow)
sql = """ sql = """
SELECT * SELECT *
FROM mrp_workorder FROM mrp_workorder
WHERE state!='rework' WHERE state!='rework'
to_char(date_planned_start::timestamp + '8 hour','YYYY-MM-DD HH:mm:SS')>= %s AND (date_planned_start + interval '8 hours') >= %s::timestamp
AND to_char(date_planned_finished::timestamp + '8 hour','YYYY-MM-DD HH:mm:SS')<= %s AND (date_planned_finished + interval '8 hours') <= %s::timestamp
""" """
params = [tomorrow_start, tomorrow_end] params = [tomorrow_start, tomorrow_end]
if production_line: if production_line:
line = self.env['sf.production.line'].search(
[('name', '=', production_line)], limit=1)
if not line:
raise ValueError(f"生产线'{production_line}'不存在")
sql += "AND production_line_id = %s" sql += "AND production_line_id = %s"
params.append(production_line) params.append(line.id)
self.env.cr.execute(sql, params) self.env.cr.execute(sql, params)
ids = [t[0] for t in self.env.cr.fetchall()] ids = [t[0] for t in self.env.cr.fetchall()]
return [('id', 'in', ids)] return [('id', 'in', ids)]
@@ -1238,12 +1255,12 @@ class ResMrpWorkOrder(models.Model):
}] }]
return workorders_values_str return workorders_values_str
def check_lot_exists(self, picking_id, lot_id): # def check_lot_exists(self, picking_id, lot_id):
return bool( # return bool(
picking_id.move_ids.move_line_ids.filtered( # picking_id.move_ids.move_line_ids.filtered(
lambda line: line.lot_id.id == lot_id # lambda line: line.lot_id.id == lot_id
) # )
) # )
def _process_compute_state(self): def _process_compute_state(self):
sorted_workorders = sorted(self, key=lambda x: x.sequence) sorted_workorders = sorted(self, key=lambda x: x.sequence)
@@ -1276,27 +1293,20 @@ class ResMrpWorkOrder(models.Model):
# for move_line in move.move_line_ids # for move_line in move.move_line_ids
# ) # )
if (workorder.production_id.production_type == '人工线下加工' if (workorder.production_id.production_type == '人工线下加工'
and workorder.production_id.programming_state == '编程'): and workorder.production_id.schedule_state == ''):
# and workorder.production_id.programming_state == '已编程' # and workorder.production_id.programming_state == '已编程'
if workorder.is_subcontract is True: if workorder.is_subcontract is True:
if workorder.production_id.state == 'rework': if workorder.production_id.state == 'rework':
workorder.state = 'waiting' workorder.state = 'waiting'
continue continue
purchase_orders_id = self._get_surface_technics_purchase_ids() purchase_orders_id = self._get_surface_technics_purchase_ids()
purchase_count = 0 if purchase_orders_id.state == 'purchase':
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' workorder.state = 'ready'
picking_id = workorder.production_id.picking_ids.filtered( # picking_id = workorder.production_id.picking_ids.filtered(
lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区') # lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
move_out = picking_id.move_ids # move_out = picking_id.move_ids
# move_out = workorder.move_subcontract_workorder_ids[1] move_out = workorder.move_subcontract_workorder_ids[1]
for mo in move_out: 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': if mo.state != 'done':
mo.write({'state': 'assigned', 'production_id': False}) mo.write({'state': 'assigned', 'production_id': False})
if not mo.move_line_ids: if not mo.move_line_ids:
@@ -1312,7 +1322,6 @@ class ResMrpWorkOrder(models.Model):
else: else:
workorder.state = 'ready' workorder.state = 'ready'
continue continue
continue
# ================= 如果制造订单刀具状态为[无效刀、缺刀] 或者 制造订单状态为[返工]========================== # ================= 如果制造订单刀具状态为[无效刀、缺刀] 或者 制造订单状态为[返工]==========================
if (workorder.production_id.tool_state in ['1', '2'] or workorder.production_id.state == 'rework' if (workorder.production_id.tool_state in ['1', '2'] or workorder.production_id.state == 'rework'
or workorder.production_id.schedule_state != '已排' or workorder.production_id.schedule_state != '已排'
@@ -1328,18 +1337,19 @@ class ResMrpWorkOrder(models.Model):
if workorder.is_subcontract is False: if workorder.is_subcontract is False:
workorder.state = 'ready' workorder.state = 'ready'
else: else:
if workorder.production_id.programming_state == '已编程': if len(workorder.production_id.picking_ids.filtered(
lambda w: w.state not in ['done',
'cancel'])) == 0 and workorder.production_id.programming_state == '已编程':
purchase_orders_id = self._get_surface_technics_purchase_ids() purchase_orders_id = self._get_surface_technics_purchase_ids()
if purchase_orders_id: if purchase_orders_id:
if purchase_orders_id.state == 'purchase': if purchase_orders_id.state == 'purchase':
workorder.state = 'ready' workorder.state = 'ready'
picking_id = workorder.production_id.picking_ids.filtered( move_out = workorder.move_subcontract_workorder_ids[1]
lambda # picking_id = workorder.production_id.picking_ids.filtered(
wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区') # lambda
move_out = picking_id.move_ids # wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
# move_out = picking_id.move_ids
for mo in move_out: 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': if mo.state != 'done':
mo.write({'state': 'assigned', 'production_id': False}) mo.write({'state': 'assigned', 'production_id': False})
if not mo.move_line_ids: if not mo.move_line_ids:
@@ -1388,16 +1398,7 @@ class ResMrpWorkOrder(models.Model):
boolean = True boolean = True
if not boolean: if not boolean:
raise UserError('制造订单【%s】缺少组件的序列号信息!' % self.production_id.name) raise UserError('制造订单【%s】缺少组件的序列号信息!' % self.production_id.name)
self.pro_code = False # 默认值 self.pro_code = self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name
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校验 # cnc校验
if self.production_id.production_type == '自动化产线加工': if self.production_id.production_type == '自动化产线加工':
cnc_workorder = self.search( cnc_workorder = self.search(
@@ -1421,9 +1422,10 @@ class ResMrpWorkOrder(models.Model):
# 表面工艺外协出库单 # 表面工艺外协出库单
if self.routing_type == '表面工艺': if self.routing_type == '表面工艺':
if self.is_subcontract is True: if self.is_subcontract is True:
picking_id = self.production_id.picking_ids.filtered( move_out = self.move_subcontract_workorder_ids[1]
lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区') # picking_id = self.production_id.picking_ids.filtered(
move_out = picking_id.move_ids # 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.move_subcontract_workorder_ids[1]
# move_out = self.env['stock.move'].search( # move_out = self.env['stock.move'].search(
# [('location_id', '=', self.env['stock.location'].search( # [('location_id', '=', self.env['stock.location'].search(
@@ -1432,8 +1434,6 @@ class ResMrpWorkOrder(models.Model):
# [('barcode', 'ilike', 'VL-SPOC')]).id), # [('barcode', 'ilike', 'VL-SPOC')]).id),
# ('origin', '=', self.production_id.name), ('state', 'not in', ['cancel', 'done'])]) # ('origin', '=', self.production_id.name), ('state', 'not in', ['cancel', 'done'])])
for mo in move_out: 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': if mo.state != 'done':
mo.write({'state': 'assigned', 'production_id': False}) mo.write({'state': 'assigned', 'production_id': False})
if not mo.move_line_ids: if not mo.move_line_ids:
@@ -1566,8 +1566,7 @@ class ResMrpWorkOrder(models.Model):
lambda wo: wo.is_subcontract == True and wo.state != 'cancel').sorted('sequence') lambda wo: wo.is_subcontract == True and wo.state != 'cancel').sorted('sequence')
if self == subcontract_workorders[-1]: if self == subcontract_workorders[-1]:
# 给下一个库存移动就绪 # 给下一个库存移动就绪
if self.move_subcontract_workorder_ids: self.move_subcontract_workorder_ids[0].move_dest_ids._action_done()
self.move_subcontract_workorder_ids[0].move_dest_ids._action_done()
# self.production_id.button_mark_done() # self.production_id.button_mark_done()
tem_date_planned_finished = record.date_planned_finished tem_date_planned_finished = record.date_planned_finished
tem_date_finished = record.date_finished tem_date_finished = record.date_finished

View File

@@ -795,7 +795,7 @@ class ResProductMo(models.Model):
for record in self: for record in self:
if record.categ_id.name == '坯料': if record.categ_id.name == '坯料':
product_name = '' product_name = ''
match = re.search(r'(S\d{5}-\d)', record.name) match = re.search(r'(S\d{5}-\d+)', record.name)
# 如果匹配成功,提取结果 # 如果匹配成功,提取结果
if match: if match:
product_name = match.group(0) product_name = match.group(0)

View File

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

View File

@@ -1,30 +1,30 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import base64 # import base64
import datetime # import datetime
import logging # import logging
import json # import json
import os # import os
import re # import re
import traceback # import traceback
from operator import itemgetter # from operator import itemgetter
import requests # import requests
from itertools import groupby # from itertools import groupby
from collections import defaultdict, namedtuple # from collections import defaultdict, namedtuple
from odoo import api, fields, models, SUPERUSER_ID, _ # from odoo import api, fields, models, SUPERUSER_ID, _
from odoo.exceptions import UserError, ValidationError # from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_compare, float_round, float_is_zero, format_datetime # from odoo.tools import float_compare, float_round, float_is_zero, format_datetime
class PurchaseRequestLine(models.Model): # class PurchaseRequestLine(models.Model):
_inherit = 'purchase.request' # _inherit = 'purchase.request'
is_subcontract = fields.Boolean(string='是否外协',default=False) # is_subcontract = fields.Boolean(string='是否外协',default=False)
class PurchaseRequestLine(models.Model): # class PurchaseRequestLine(models.Model):
_inherit = 'purchase.request.line' # _inherit = 'purchase.request.line'
is_subcontract = fields.Boolean(string='是否外协') # is_subcontract = fields.Boolean(string='是否外协')
class PurchaseRequest(models.Model): # class PurchaseRequest(models.Model):
_inherit = 'purchase.request' # _inherit = 'purchase.request'
bom_id = fields.Many2one('mrp.bom') # bom_id = fields.Many2one('mrp.bom')

View File

@@ -58,8 +58,8 @@ class SaleOrder(models.Model):
# 复制成品模板上的属性 # 复制成品模板上的属性
line.product_id.product_tmpl_id.copy_template(product_template_id) line.product_id.product_tmpl_id.copy_template(product_template_id)
# 将模板上的single_manufacturing属性复制到成品上 # 将模板上的single_manufacturing属性复制到成品上
line.product_id.single_manufacturing = product_template_id.single_manufacturing # line.product_id.single_manufacturing = product_template_id.single_manufacturing
line.product_id.tracking = product_template_id.tracking # line.product_id.tracking = product_template_id.tracking
order_id = self order_id = self
product = line.product_id product = line.product_id
@@ -76,7 +76,7 @@ class SaleOrder(models.Model):
'embryo_redundancy_id': line.embryo_redundancy_id, 'embryo_redundancy_id': line.embryo_redundancy_id,
} }
product_name = '' product_name = ''
match = re.search(r'(S\d{5}-\d)', product.name) match = re.search(r'(S\d{5}-\d+)', product.name)
# 如果匹配成功,提取结果 # 如果匹配成功,提取结果
if match: if match:
product_name = match.group(0) product_name = match.group(0)
@@ -190,7 +190,30 @@ class SaleOrder(models.Model):
'target': 'new', 'target': 'new',
'res_id': wizard.id, 'res_id': wizard.id,
} }
def create_sale_documents(self, contract_file_name, contract_file):
# 创建ir.attachment记录
attachment = self.env['ir.attachment'].sudo().create({
'name': contract_file_name,
'type': 'binary',
'datas': contract_file,
'res_model': 'sale.order',
})
# 获取默认的文档文件夹
workspace = self.env.ref('sf_sale.documents_sales_contracts_folder_1').id
# 创建 documents.document 记录
document = self.env['documents.document'].sudo().create({
'name': contract_file_name,
'attachment_id': attachment.id,
'folder_id': workspace,
'res_model': 'sale.order',
'res_id': self.id,
})
self.write({
'contract_document_id': document.id
})
class SaleOrderLine(models.Model): class SaleOrderLine(models.Model):
_inherit = 'sale.order.line' _inherit = 'sale.order.line'

View File

@@ -7,73 +7,74 @@ from odoo.exceptions import UserError, ValidationError
class SfProductionProcessParameter(models.Model): class SfProductionProcessParameter(models.Model):
_inherit = 'sf.production.process.parameter' _inherit = 'sf.production.process.parameter'
service_products = fields.Many2one( # service_products = fields.Many2one(
'product.template', # 'product.template',
string='外协服务产品',compute='_compute_service_products',inverse='_inverse_service_products', # string='外协服务产品',compute='_compute_service_products',inverse='_inverse_service_products',
store=True # store=True
) # )
outsourced_service_products = fields.One2many( # outsourced_service_products = fields.One2many(
'product.template', # 另一个模型的名称 # 'product.template', # 另一个模型的名称
'server_product_process_parameters_id', # 对应的 Many2one 字段名称 # 'server_product_process_parameters_id', # 对应的 Many2one 字段名称
string='外协服务产品' # string='外协服务产品'
) # )
is_product_button = fields.Boolean(compute='_compute_is_product_button',default=False) # is_product_button = fields.Boolean(compute='_compute_is_product_button',default=False)
is_delete_button = fields.Boolean(compute='_compute_is_delete_button', default=False) # is_delete_button = fields.Boolean(compute='_compute_is_delete_button', default=False)
routing_id = fields.Many2one('mrp.routing.workcenter', string="工序") # routing_id = fields.Many2one('mrp.routing.workcenter', string="工序")
def _inverse_service_products(self):
for record in self:
if record.service_products:
# 确保关联关系正确
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): # @api.depends('outsourced_service_products')
result = [] # def _compute_service_products(self):
for record in self: # for record in self:
name = f"{record.process_id.name} - {record.name}" # 自定义显示格式 # # 假设取第一条作为主明细
result.append((record.id, name)) # record.service_products = record.outsourced_service_products.ids if record.outsourced_service_products else False
return result
@api.constrains('outsourced_service_products')
def _validate_partner_limit(self):
for record in self:
if len(record.outsourced_service_products) > 1:
raise ValidationError("工艺参数不能与多个产品关联")
@api.onchange('outsourced_service_products') # def _inverse_service_products(self):
def _onchange_validate_partner_limit(self): # for record in self:
for record in self: # if record.service_products:
if len(record.outsourced_service_products) > 1: # # 确保关联关系正确
raise ValidationError("工艺参数不能与多个产品关联") # record.outsourced_service_products = record.service_products.ids if record.service_products else False
@api.depends('outsourced_service_products') # else:
def _compute_is_product_button(self): # record.outsourced_service_products = False
for record in self: # def name_get(self):
if record.outsourced_service_products: # result = []
record.is_product_button = True # for record in self:
else: # name = f"{record.process_id.name} - {record.name}" # 自定义显示格式
record.is_product_button = False # result.append((record.id, name))
# return result
# @api.constrains('outsourced_service_products')
# def _validate_partner_limit(self):
# for record in self:
# if len(record.outsourced_service_products) > 1:
# raise ValidationError("工艺参数不能与多个产品关联")
def has_wksp_prefix(self): # @api.onchange('outsourced_service_products')
""" # def _onchange_validate_partner_limit(self):
判断字符串是否以WKSP开头不区分大小写 # for record in self:
:param text: 要检查的字符串 # if len(record.outsourced_service_products) > 1:
:return: True/False # raise ValidationError("工艺参数不能与多个产品关联")
""" # @api.depends('outsourced_service_products')
return self.code.upper().startswith('101'+self.routing_id.code) # def _compute_is_product_button(self):
@api.depends('outsourced_service_products','code') # for record in self:
def _compute_is_delete_button(self): # if record.outsourced_service_products:
for record in self: # record.is_product_button = True
if record.outsourced_service_products and record.has_wksp_prefix(): # else:
record.is_delete_button = False # record.is_product_button = False
elif record.outsourced_service_products:
record.is_delete_button = True # def has_wksp_prefix(self):
else: # """
record.is_delete_button = True # 判断字符串是否以WKSP开头不区分大小写
# :param text: 要检查的字符串
# :return: True/False
# """
# return self.code.upper().startswith('101'+self.routing_id.code)
# @api.depends('outsourced_service_products','code')
# def _compute_is_delete_button(self):
# for record in self:
# if record.outsourced_service_products and record.has_wksp_prefix():
# record.is_delete_button = False
# elif record.outsourced_service_products:
# record.is_delete_button = True
# else:
# record.is_delete_button = True
@api.model @api.model
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
if self._context.get('route_id'): if self._context.get('route_id'):
@@ -89,19 +90,19 @@ class SfProductionProcessParameter(models.Model):
return self._search(domain, limit=limit, access_rights_uid=name_get_uid) return self._search(domain, limit=limit, access_rights_uid=name_get_uid)
return super()._name_search(name, args, operator, limit, name_get_uid) return super()._name_search(name, args, operator, limit, name_get_uid)
def action_create_service_product(self): # def action_create_service_product(self):
if self.id: # 如果是已存在的记录 # if self.id: # 如果是已存在的记录
self.write({}) # 空写入会触发保存 # self.write({}) # 空写入会触发保存
else: # 如果是新记录 # else: # 如果是新记录
self = self.create(self._convert_to_write(self.read()[0])) # self = self.create(self._convert_to_write(self.read()[0]))
return { # return {
'type': 'ir.actions.act_window', # 'type': 'ir.actions.act_window',
'name': '向导名称', # 'name': '向导名称',
'res_model': 'product.creation.wizard', # 'res_model': 'product.creation.wizard',
'view_mode': 'form', # 'view_mode': 'form',
'target': 'new', # 'target': 'new',
'context': {'default_process_parameter_id': self.id}, # 传递当前记录ID # 'context': {'default_process_parameter_id': self.id}, # 传递当前记录ID
} # }
# #
# return { # return {
# 'name': '创建服务产品', # 'name': '创建服务产品',
@@ -115,6 +116,6 @@ class SfProductionProcessParameter(models.Model):
# }, # },
# } # }
def action_hide_service_products(self): # def action_hide_service_products(self):
# self.outsourced_service_products.active = False # # self.outsourced_service_products.active = False
self.active = False # self.active = False

View File

@@ -564,6 +564,13 @@ class StockPicking(models.Model):
part_numbers = fields.Char(string="零件图号", compute='_compute_part_info', store=True, index=True) part_numbers = fields.Char(string="零件图号", compute='_compute_part_info', store=True, index=True)
part_names = fields.Char(string="零件名称", compute='_compute_part_info', store=True, index=True) part_names = fields.Char(string="零件名称", compute='_compute_part_info', store=True, index=True)
model_id = fields.Char('模型ID', compute='_compute_model_id', store=True, index=True)
@api.depends('move_ids_without_package.model_id')
def _compute_model_id(self):
for picking in self:
model_id = picking.move_ids_without_package.mapped('model_id')
picking.model_id = ','.join(filter(None, model_id))
@api.depends('move_ids_without_package.part_number', 'move_ids_without_package.part_name') @api.depends('move_ids_without_package.part_number', 'move_ids_without_package.part_name')
def _compute_part_info(self): def _compute_part_info(self):
@@ -631,62 +638,84 @@ class StockPicking(models.Model):
move.action_clear_lines_show_details() move.action_clear_lines_show_details()
move.action_show_details() move.action_show_details()
res = super().button_validate() res = super().button_validate()
lot_ids = None # lot_ids = None
product_ids = self.move_ids.mapped('product_id') # 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': # 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') # 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'] # 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 == '制造前': # if res and self.location_id.name == '外协收料区' and self.location_dest_id.name == '制造前':
# 如果是最后一张外协入库单,则设置库存位置的预留数量 # # 如果是最后一张外协入库单,则设置库存位置的预留数量
for production_id in production_ids: # for production_id in production_ids:
if lot_ids: # if lot_ids:
lot_id = production_id.move_raw_ids.move_line_ids.lot_id # lot_id = production_id.move_raw_ids.move_line_ids.lot_id
# picking_ids = production_id.picking_ids.filtered( # # picking_ids = production_id.picking_ids.filtered(
# lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前') # # lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前')
if lot_id in lot_ids: # if lot_id in lot_ids:
workorder_id = production_id.workorder_ids.filtered( # workorder_id = production_id.workorder_ids.filtered(
lambda a: a.state == 'progress' and a.is_subcontract) # lambda a: a.state == 'progress' and a.is_subcontract)
if not workorder_id: # if not workorder_id:
continue # continue
workorder_id.button_finish() # workorder_id.button_finish()
else: # else:
workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'progress' and a.is_subcontract) # workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'progress' and a.is_subcontract)
if not workorder_id: # if not workorder_id:
continue # continue
workorder_id.button_finish() # workorder_id.button_finish()
# lot_id = workorder.production_id.move_raw_ids.move_line_ids.lot_id # # lot_id = workorder.production_id.move_raw_ids.move_line_ids.lot_id
# picking_ids = workorder.production_id.picking_ids.filtered( # # picking_ids = workorder.production_id.picking_ids.filtered(
# lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前') # # lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前')
# if move_in: # # if move_in:
# workorder = move_in.subcontract_workorder_id # # workorder = move_in.subcontract_workorder_id
# workorders = workorder.production_id.workorder_ids # # workorders = workorder.production_id.workorder_ids
# subcontract_workorders = workorders.filtered( # # subcontract_workorders = workorders.filtered(
# lambda wo: wo.is_subcontract == True and wo.state != 'cancel').sorted('sequence') # # lambda wo: wo.is_subcontract == True and wo.state != 'cancel').sorted('sequence')
# # if workorder == subcontract_workorders[-1]: # # # if workorder == subcontract_workorders[-1]:
# # self.env['stock.quant']._update_reserved_quantity( # # # self.env['stock.quant']._update_reserved_quantity(
# # move_in.product_id, move_in.location_dest_id, move_in.product_uom_qty, # # # move_in.product_id, move_in.location_dest_id, move_in.product_uom_qty,
# # lot_id=move_in.move_line_ids.lot_id, # # # lot_id=move_in.move_line_ids.lot_id,
# # package_id=False, owner_id=False, strict=False # # # package_id=False, owner_id=False, strict=False
# # ) # # # )
# workorder.button_finish() # # workorder.button_finish()
if res and self.location_id.name == '制造前' and self.location_dest_id.name == '外协加工区': # if res and self.location_id.name == '制造前' and self.location_dest_id.name == '外协加工区':
for production_id in production_ids: # for production_id in production_ids:
if lot_ids: # if lot_ids:
lot_id = production_id.move_raw_ids.move_line_ids.lot_id # lot_id = production_id.move_raw_ids.move_line_ids.lot_id
# picking_ids = production_id.picking_ids.filtered( # # picking_ids = production_id.picking_ids.filtered(
# lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前') # # lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前')
if lot_id in lot_ids: # if lot_id in lot_ids:
workorder_id = production_id.workorder_ids.filtered( # workorder_id = production_id.workorder_ids.filtered(
lambda a: a.state == 'progress' and a.is_subcontract) # lambda a: a.state == 'progress' and a.is_subcontract)
if not workorder_id: # if not workorder_id:
continue # continue
workorder_id.button_finish() # workorder_id.button_finish()
else: # else:
workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'ready' and a.is_subcontract) # workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'ready' and a.is_subcontract)
if not workorder_id: # if not workorder_id:
continue # continue
workorder_id.button_start() # workorder_id.button_start()
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:
# 如果是最后一张外协入库单,则设置库存位置的预留数量
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()
if self.location_id.name == '成品存货区' and self.location_dest_id.name == '客户': if self.location_id.name == '成品存货区' and self.location_dest_id.name == '客户':
sale_id = self.env['sale.order'].sudo().search( sale_id = self.env['sale.order'].sudo().search(
[('name', '=', self.origin)]) [('name', '=', self.origin)])
@@ -817,6 +846,7 @@ class ReStockMove(models.Model):
materiel_height = fields.Float(string='物料高度', digits=(16, 4)) materiel_height = fields.Float(string='物料高度', digits=(16, 4))
part_number = fields.Char(string='零件图号', compute='_compute_part_info', store=True) part_number = fields.Char(string='零件图号', compute='_compute_part_info', store=True)
part_name = fields.Char(string='零件名称', compute='_compute_part_info', store=True) part_name = fields.Char(string='零件名称', compute='_compute_part_info', store=True)
model_id = fields.Char('模型ID', related='product_id.model_id')
@api.depends('product_id') @api.depends('product_id')
def _compute_part_info(self): def _compute_part_info(self):
@@ -827,7 +857,7 @@ class ReStockMove(models.Model):
move.part_name = move.product_id.part_name move.part_name = move.product_id.part_name
elif move.product_id.categ_id.type == '坯料': elif move.product_id.categ_id.type == '坯料':
product_name = '' product_name = ''
match = re.search(r'(S\d{5}-\d)', move.product_id.name) match = re.search(r'(S\d{5}-\d+)', move.product_id.name)
# 如果匹配成功,提取结果 # 如果匹配成功,提取结果
if match: if match:
product_name = match.group(0) product_name = match.group(0)
@@ -859,7 +889,7 @@ class ReStockMove(models.Model):
continue continue
product_name = '' product_name = ''
logging.info('制造订单的产品 %s', production_id.product_id.name) logging.info('制造订单的产品 %s', production_id.product_id.name)
match = re.search(r'(S\d{5}-\d)', production_id.product_id.name) match = re.search(r'(S\d{5}-\d+)', production_id.product_id.name)
# 如果匹配成功,提取结果 # 如果匹配成功,提取结果
if match: if match:
product_name = match.group(0) product_name = match.group(0)
@@ -930,8 +960,6 @@ class ReStockMove(models.Model):
} }
def get_move_line(self, production_id, sorted_workorders): 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 { return {
'move_id': self.id, 'move_id': self.id,
'product_id': self.product_id.id, 'product_id': self.product_id.id,
@@ -939,7 +967,7 @@ class ReStockMove(models.Model):
'location_id': self.picking_id.location_id.id, 'location_id': self.picking_id.location_id.id,
'location_dest_id': self.picking_id.location_dest_id.id, 'location_dest_id': self.picking_id.location_dest_id.id,
'picking_id': self.picking_id.id, 'picking_id': self.picking_id.id,
'reserved_uom_qty': qty, 'reserved_uom_qty': self.product_uom_qty,
'lot_id': production_id.move_line_raw_ids.lot_id.id, 'lot_id': production_id.move_line_raw_ids.lot_id.id,
'company_id': self.env.company.id, 'company_id': self.env.company.id,
# 'workorder_id': '' if not sorted_workorders else sorted_workorders.id, # 'workorder_id': '' if not sorted_workorders else sorted_workorders.id,

View File

@@ -383,7 +383,7 @@
<field name="process_parameters_id" <field name="process_parameters_id"
attrs="{'readonly': [('id', '!=', False),('routing_tag', '=', 'standard')]}" attrs="{'readonly': [('id', '!=', False),('routing_tag', '=', 'standard')]}"
string="参数" context="{'route_id':route_id,'production_id': production_id}" string="参数" context="{'route_id':route_id,'production_id': production_id}"
options="{'no_create': True}" domain="[('routing_id', '=', 'route_id')]"/> options="{'no_create': True}"/>
<field name="panel" readonly="1"/> <field name="panel" readonly="1"/>
<field name="routing_tag" readonly="1" widget="badge" <field name="routing_tag" readonly="1" widget="badge"
decoration-success="routing_tag == 'standard'" decoration-success="routing_tag == 'standard'"
@@ -427,6 +427,7 @@
<field name="programming_method"/> <field name="programming_method"/>
<field name="current_programming_count"/> <field name="current_programming_count"/>
<field name="target_production_id"/> <field name="target_production_id"/>
<field name="apply_uid"/>
<field name="apply_time"/> <field name="apply_time"/>
<field name="send_time"/> <field name="send_time"/>
</tree> </tree>
@@ -602,6 +603,7 @@
<field name="part_number"/> <field name="part_number"/>
<field name="sale_order_id"/> <field name="sale_order_id"/>
<field name="deadline_of_delivery" icon="fa-calendar" enable_counters="1" filter_domain="[('deadline_of_delivery', 'ilike', self)]"/> <field name="deadline_of_delivery" icon="fa-calendar" enable_counters="1" filter_domain="[('deadline_of_delivery', 'ilike', self)]"/>
<field name="model_id"/>
</xpath> </xpath>
<xpath expr="//field[@name='product_variant_attributes']" position="attributes"> <xpath expr="//field[@name='product_variant_attributes']" position="attributes">
<attribute name="invisible">1</attribute> <attribute name="invisible">1</attribute>

View File

@@ -22,7 +22,7 @@
<field name="is_repeat"/> <field name="is_repeat"/>
<field name="reserved_duration"/> <field name="reserved_duration"/>
</field> </field>
<xpath expr="//notebook/page[1]" position="before"> <!-- <xpath expr="//notebook/page[1]" position="before">
<page string="可选工艺参数"> <page string="可选工艺参数">
<field name="optional_process_parameters"> <field name="optional_process_parameters">
<tree editable="bottom"> <tree editable="bottom">
@@ -30,9 +30,9 @@
<field name="is_delete_button" invisible="1"/> <field name="is_delete_button" invisible="1"/>
<field name="code" attrs="{'readonly': True}"/> <field name="code" attrs="{'readonly': True}"/>
<field name="name" required="1"/> <field name="name" required="1"/>
<field name="service_products" domain="[('detailed_type', '=', 'service'),('server_product_process_parameters_id', '=', False)]"/> <field name="service_products" domain="[('detailed_type', '=', 'service'),('server_product_process_parameters_id', '=', False)]"/> -->
<!-- 按钮列 --> <!-- 按钮列 -->
<button name="action_create_service_product" string="创建服务产品" type="object" <!-- <button name="action_create_service_product" string="创建服务产品" type="object"
class="btn-primary" class="btn-primary"
attrs="{'invisible': [('is_product_button', '=', True)]}" context="{'default_process_parameter_id':id}"/> attrs="{'invisible': [('is_product_button', '=', True)]}" context="{'default_process_parameter_id':id}"/>
<button name="action_hide_service_products" string="删除" type="object" <button name="action_hide_service_products" string="删除" type="object"
@@ -41,7 +41,7 @@
</tree> </tree>
</field> </field>
</page> </page>
</xpath> </xpath> -->
</field> </field>
</record> </record>
</data> </data>

View File

@@ -144,7 +144,7 @@
statusbar_visible="pending,waiting,ready,progress,to be detected,done,rework"/> statusbar_visible="pending,waiting,ready,progress,to be detected,done,rework"/>
</xpath> </xpath>
<xpath expr="//div[@name='button_box']" position="inside"> <xpath expr="//div[@name='button_box']" position="inside">
<button type="object" name="action_view_pr_mrp_workorder" class="oe_stat_button" <!-- <button type="object" name="action_view_pr_mrp_workorder" class="oe_stat_button"
icon="fa-credit-card" icon="fa-credit-card"
groups="base.group_user,sf_base.group_sf_order_user" groups="base.group_user,sf_base.group_sf_order_user"
attrs="{'invisible': [('pr_mp_count', '=', 0)]}"> attrs="{'invisible': [('pr_mp_count', '=', 0)]}">
@@ -154,7 +154,7 @@
</span> </span>
<span class="o_stat_text">采购申请</span> <span class="o_stat_text">采购申请</span>
</div> </div>
</button> </button> -->
<button type="object" name="action_view_surface_technics_purchase" class="oe_stat_button" <button type="object" name="action_view_surface_technics_purchase" class="oe_stat_button"
icon="fa-credit-card" icon="fa-credit-card"
groups="base.group_user,sf_base.group_sf_order_user" groups="base.group_user,sf_base.group_sf_order_user"
@@ -677,8 +677,9 @@
<field name="inherit_id" ref="mrp.view_mrp_production_work_order_search"/> <field name="inherit_id" ref="mrp.view_mrp_production_work_order_search"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="product_id" position="after"> <field name="product_id" position="after">
<field name="part_number" string="成品零件图号"/> <field name="part_number" string="零件图号"/>
<field name="model_id" string="模型id"/> <field name="part_name" string="零件名称"/>
<field name="model_id" string="模型ID"/>
</field> </field>
<xpath expr="//filter[@name='progress']" position="after"> <xpath expr="//filter[@name='progress']" position="after">
<filter string="待检测" name="state" domain="[('state','=','to be detected')]"/> <filter string="待检测" name="state" domain="[('state','=','to be detected')]"/>

View File

@@ -12,5 +12,18 @@
</xpath> </xpath>
</field> </field>
</record> </record>
<record id="product_template_search_inherit_sf_manufacturing" model="ir.ui.view">
<field name="name">product.template.search</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_search_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='categ_id']" position="after">
<field name="part_number" string="零件图号"/>
<field name="part_name" string="零件名称"/>
<field name="model_id" string="模型ID"/>
</xpath>
</field>
</record>
</data> </data>
</odoo> </odoo>

View File

@@ -73,6 +73,7 @@
<xpath expr="//field[@name='picking_type_id']" position="after"> <xpath expr="//field[@name='picking_type_id']" position="after">
<field name="part_numbers" string="零件图号" filter_domain="[('part_numbers', 'ilike', self)]"/> <field name="part_numbers" string="零件图号" filter_domain="[('part_numbers', 'ilike', self)]"/>
<field name="part_names" string="零件名称" filter_domain="[('part_names', 'ilike', self)]"/> <field name="part_names" string="零件名称" filter_domain="[('part_names', 'ilike', self)]"/>
<field name="model_id" string="模型ID" filter_domain="[('model_id', 'ilike', self)]"/>
</xpath> </xpath>
</field> </field>
</record> </record>

View File

@@ -77,11 +77,11 @@ class ProductionTechnologyReAdjustWizard(models.TransientModel):
if workorders[ if workorders[
0].production_id.product_id.categ_id.type == '成品' and item.programming_state != '已编程': 0].production_id.product_id.categ_id.type == '成品' and item.programming_state != '已编程':
workorders[0].state = 'waiting' workorders[0].state = 'waiting'
pr_ids = self.env['purchase.request'].sudo().search( # pr_ids = self.env['purchase.request'].sudo().search(
[('origin', 'like', item.name), ('is_subcontract', '=', 'True'), ('state', '!=', 'rejected')]) # [('origin', 'like', item.name), ('is_subcontract', '=', 'True'), ('state', '!=', 'rejected')])
if not pr_ids: # if not pr_ids:
continue # continue
if not all(pr.state == 'draft' for pr in pr_ids): # if not all(pr.state == 'draft' for pr in pr_ids):
# 如果发现有记录的 state 不是 'draft',抛出异常 # # 如果发现有记录的 state 不是 'draft',抛出异常
raise UserError("有采购申请的状态不是 '草稿'") # raise UserError("有采购申请的状态不是 '草稿'")
pr_ids.state = 'rejected' # pr_ids.state = 'rejected'

View File

@@ -125,11 +125,17 @@ class ReworkWizard(models.TransientModel):
# 1、单独返工CNC工单则不解绑托盘RFID如单独返工装夹预调工单则自动解绑托盘RFID # 1、单独返工CNC工单则不解绑托盘RFID如单独返工装夹预调工单则自动解绑托盘RFID
# 2、返工CNC工单和装夹预调工单则自动解绑RFID # 2、返工CNC工单和装夹预调工单则自动解绑RFID
clamp_workorder_ids = rework_workorder_ids.filtered(lambda rp: rp.routing_type == '装夹预调') clamp_workorder_ids = rework_workorder_ids.filtered(lambda rp: rp.routing_type == '装夹预调')
vals_list = [{
'id': order.id,
'rfid_code_old': order.rfid_code,
'rfid_code': False
} for order in rework_workorder_ids]
rework_workorder_ids.write(vals_list)
if clamp_workorder_ids: if clamp_workorder_ids:
for clamp_workorder_id in clamp_workorder_ids: for clamp_workorder_id in clamp_workorder_ids:
self.production_id.workorder_ids.filtered( self.production_id.workorder_ids.filtered(lambda wk: (
lambda wk: wk.processing_panel == clamp_workorder_id.processing_panel).write( wk.processing_panel == clamp_workorder_id.processing_panel)).write({'rfid_code': None})
{'rfid_code': None})
# 返工工单状态设置为【返工】 # 返工工单状态设置为【返工】
rework_workorder_ids.write({'state': 'rework'}) rework_workorder_ids.write({'state': 'rework'})
# 查询返工工单对应的工艺设计记录,并调用方法拼接数据,用于创建新的工单 # 查询返工工单对应的工艺设计记录,并调用方法拼接数据,用于创建新的工单

View File

@@ -126,6 +126,11 @@
<field name="model">mrp.production</field> <field name="model">mrp.production</field>
</record> </record>
<record id="bussiness_purchase_request" model="jikimo.message.bussiness.node">
<field name="name">采购申请待处理通知</field>
<field name="model">purchase.request</field>
</record>
<record id="bussiness_outsourcing" model="jikimo.message.bussiness.node"> <record id="bussiness_outsourcing" model="jikimo.message.bussiness.node">
<field name="name">委外加工采购单提醒</field> <field name="name">委外加工采购单提醒</field>
<field name="model">purchase.order</field> <field name="model">purchase.order</field>
@@ -156,9 +161,13 @@
<field name="model">purchase.order</field> <field name="model">purchase.order</field>
</record> </record>
<record id="bussiness_quality_check" model="jikimo.message.bussiness.node"> <!-- <record id="bussiness_quality_check" model="jikimo.message.bussiness.node">-->
<field name="name">待质检提醒</field> <!-- <field name="name">待质检提醒</field>-->
<field name="model">product.product</field> <!-- <field name="model">product.product</field>-->
<!-- </record>-->
<record id="bussiness_quality_check_none" model="jikimo.message.bussiness.node">
<field name="name">待质检</field>
<field name="model">quality.check</field>
</record> </record>
</data> </data>

View File

@@ -339,6 +339,18 @@
事项:请确认委外采购单并处理。</field> 事项:请确认委外采购单并处理。</field>
</record> </record>
<record id="template_purchase_request" model="jikimo.message.template">
<field name="name">采购申请待处理通知</field>
<field name="model_id" ref="purchase_request.model_purchase_request"/>
<field name="model">purchase.request</field>
<field name="bussiness_node_id" ref="bussiness_purchase_request"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 采购申请待处理提醒:
单号:[{{name}}]({{request_url}})
事项:您有一张新的采购申请单待处理。</field>
</record>
<record id="template_purchase_remind" model="jikimo.message.template"> <record id="template_purchase_remind" model="jikimo.message.template">
<field name="name">外购订单采购单提醒</field> <field name="name">外购订单采购单提醒</field>
<field name="model_id" ref="purchase.model_purchase_order"/> <field name="model_id" ref="purchase.model_purchase_order"/>
@@ -402,16 +414,28 @@
</field> </field>
</record> </record>
<record id="template_quality_check" model="jikimo.message.template"> <!-- <record id="template_quality_check" model="jikimo.message.template">-->
<!-- <field name="name">待质检提醒</field>-->
<!-- <field name="model_id" ref="product.model_product_product"/>-->
<!-- <field name="model">product.product</field>-->
<!-- <field name="bussiness_node_id" ref="bussiness_quality_check"/>-->
<!-- <field name="msgtype">markdown</field>-->
<!-- <field name="urgency">normal</field>-->
<!-- <field name="content">### 待质检提醒:-->
<!--单号:产品[{{name}}]({{url}})-->
<!--事项:有{{num}}个质检单需要处理。</field>-->
<!-- </record>-->
<!-- </data>-->
<record id="template_quality_check_none" model="jikimo.message.template">
<field name="name">待质检提醒</field> <field name="name">待质检提醒</field>
<field name="model_id" ref="product.model_product_product"/> <field name="model_id" ref="quality.model_quality_check"/>
<field name="model">product.product</field> <field name="model">quality.check</field>
<field name="bussiness_node_id" ref="bussiness_quality_check"/> <field name="bussiness_node_id" ref="bussiness_quality_check_none"/>
<field name="msgtype">markdown</field> <field name="msgtype">markdown</field>
<field name="urgency">normal</field> <field name="urgency">normal</field>
<field name="content">### 待质检提醒: <field name="content">### 待质检提醒:
单号:产品[{{name}}]({{url}}) 单号:[{{name}}]({{url}})
事项:有{{num}}个质检单需要处理。</field> 事项:有个质检单需要处理({{type_name}})</field>
</record> </record>
</data> </data>

View File

@@ -14,3 +14,4 @@ from . import sf_message_mrp_production_wizard
from . import sf_message_mrp_production_adjust_wizard from . import sf_message_mrp_production_adjust_wizard
from . import sf_message_product from . import sf_message_product
from . import sf_message_quality_check from . import sf_message_quality_check
from . import sf_message_purchase_request

View File

@@ -29,18 +29,18 @@ class SFMessageProduct(models.Model):
'{{number}}', str(production_num)).replace( '{{number}}', str(production_num)).replace(
'{{request_url}}', url) '{{request_url}}', url)
contents.append(content) contents.append(content)
if message_queue_id.message_template_id.name == '待质检提醒': # if message_queue_id.message_template_id.name == '待质检提醒':
content = message_queue_id.message_template_id.content # content = message_queue_id.message_template_id.content
product_product = self.env['product.product'].sudo().search([('id', '=', int(message_queue_id.res_id))]) # product_product = self.env['product.product'].sudo().search([('id', '=', int(message_queue_id.res_id))])
quality_check_num = self.env['quality.check'].sudo().search_count( # quality_check_num = self.env['quality.check'].sudo().search_count(
[('product_id', '=', product_product.id), ('quality_state', '=', 'none')]) # [('product_id', '=', product_product.id), ('quality_state', '=', 'none')])
if quality_check_num >= 1: # if quality_check_num >= 1:
url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') # url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
action_id = self.env.ref('quality_control.quality_check_action_report').id # action_id = self.env.ref('quality_control.quality_check_action_report').id
url_with_id = f"{url}/web#view_type=list&action={action_id}" # url_with_id = f"{url}/web#view_type=list&action={action_id}"
content = content.replace('{{name}}', product_product.name).replace('{{url}}', url_with_id).replace( # content = content.replace('{{name}}', product_product.name).replace('{{url}}', url_with_id).replace(
'{{num}}', str(quality_check_num)) # '{{num}}', str(quality_check_num))
contents.append(content) # contents.append(content)
return contents, message_queue_ids return contents, message_queue_ids
def get_request_url(self, routing_type): def get_request_url(self, routing_type):

View File

@@ -0,0 +1,43 @@
import logging
import re
from odoo import models, fields, api, _
from urllib.parse import urlencode
class SFMessagePurchaseRequest(models.Model):
_name = 'purchase.request'
_description = "采购申请"
_inherit = ['purchase.request', 'jikimo.message.dispatch']
def write(self, vals):
original_state = self.state
res = super(SFMessagePurchaseRequest, self).write(vals)
if vals.get('state') == 'approved' and original_state != 'approved':
self.add_queue('采购申请待处理通知')
return res
def _get_message(self, message_queue_ids):
contents = []
for message_queue_id in message_queue_ids:
if message_queue_id.message_template_id.name == '采购申请待处理通知':
content = message_queue_id.message_template_id.content
url = self.request_url(int(message_queue_id.res_id))
request_line = self.env['purchase.request'].search([('id', '=', int(message_queue_id.res_id))])
content = content.replace('{{name}}', request_line.name).replace(
'{{request_url}}', url)
contents.append(content)
return contents, message_queue_ids
def request_url(self, id):
url = self.env['ir.config_parameter'].get_param('web.base.url')
action_id = self.env.ref('purchase_request.purchase_request_form_action').id
menu_id = self.env.ref('purchase_request.menu_purchase_request_act').id
# 查询参数
params = {'id': id, 'menu_id': menu_id, 'action': action_id,
'model': 'purchase.request',
'view_type': 'form'}
# 拼接查询参数
query_string = urlencode(params)
# 拼接URL
full_url = url + "/web#" + query_string
return full_url

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging import logging
from odoo import models, fields, api, _ from odoo import models, fields, api, _
from urllib.parse import urlencode
class SFMessageQualityCheck(models.Model): class SFMessageQualityCheck(models.Model):
@@ -10,25 +11,70 @@ class SFMessageQualityCheck(models.Model):
@api.model_create_multi @api.model_create_multi
def create(self, vals_list): def create(self, vals_list):
result = super().create(vals_list) result = super().create(vals_list)
try: # try:
# 判断是否为web页面创建请求 # # 判断是否为web页面创建请求
is_web_request = self.env.context.get('is_web_request', False) # is_web_request = self.env.context.get('is_web_request', False)
if not is_web_request: # if not is_web_request:
for obj in result: # for obj in result:
jikimo_message_queue = self.get_message_queue(obj.product_id.id) # jikimo_message_queue = self.get_message_queue(obj.product_id.id)
if not jikimo_message_queue: # if not jikimo_message_queue:
obj.product_id.add_queue('待质检提醒') # obj.product_id.add_queue('待质检提醒')
except Exception as e: # except Exception as e:
logging.info('add_queue待质检提醒 error:%s' % e) # logging.info('add_queue待质检提醒 error:%s' % e)
qc_ids = result.filtered(lambda qc: qc.quality_state == 'none')
if qc_ids:
message_template_id = self.env['jikimo.message.template'].sudo().search([('name', '=', '待质检提醒')])
for qc in qc_ids:
queue_id = self.env['jikimo.message.queue'].sudo().search(
[('message_template_id', '=', message_template_id[-1].id), ('res_id', '=', qc.id)])
if not queue_id and '制造' not in [pt.name for pt in qc.point_id.picking_type_ids]:
qc.add_queue('待质检')
return result return result
def get_message_queue(self, res_id): #
business_node_id = self.env.ref('sf_message.bussiness_quality_check').id # def get_message_queue(self, res_id):
message_template = self.env["jikimo.message.template"].sudo().search([ # business_node_id = self.env.ref('sf_message.bussiness_quality_check').id
("name", "=", '待质检提醒'), # message_template = self.env["jikimo.message.template"].sudo().search([
("bussiness_node_id", "=", business_node_id) # ("name", "=", '待质检提醒'),
], limit=1) # ("bussiness_node_id", "=", business_node_id)
jikimo_message_queue = self.env['jikimo.message.queue'].sudo().search( # ], limit=1)
[('res_id', '=', res_id), ("message_status", "in", ("pending", "sent")), # jikimo_message_queue = self.env['jikimo.message.queue'].sudo().search(
('message_template_id', '=', message_template.id)]) # [('res_id', '=', res_id), ("message_status", "in", ("pending", "sent")),
return jikimo_message_queue # ('message_template_id', '=', message_template.id)])
# return jikimo_message_queue
def write(self, vals):
original_state = self.quality_state
res = super(SFMessageQualityCheck, self).write(vals)
if res and vals.get('quality_state') == 'none' and original_state != 'none':
message_template_id = self.env['jikimo.message.template'].sudo().search([('name', '=', '待质检提醒')])
queue_id = self.env['jikimo.message.queue'].sudo().search(
[('message_template_id', '=', message_template_id[-1].id), ('res_id', '=', self.id)])
if not queue_id and '制造' not in [pt.name for pt in self.point_id.picking_type_ids]:
self.add_queue('待质检')
return res
def _get_message(self, message_queue_ids):
contents = []
for message_queue_id in message_queue_ids:
if message_queue_id.message_template_id.name == '待质检提醒':
content = message_queue_id.message_template_id.content
qc_line = self.search([('id', '=', int(message_queue_id.res_id))])
url = self.request_url(int(message_queue_id.res_id))
content = content.replace('{{name}}', qc_line.name).replace(
'{{url}}', url).replace('{{type_name}}', qc_line.point_id.title)
contents.append(content)
return contents, message_queue_ids
def request_url(self, id):
url = self.env['ir.config_parameter'].get_param('web.base.url')
action_id = self.env.ref('quality_control.quality_check_action_main').id
menu_id = self.env.ref('quality_control.menu_quality_checks').id
# 查询参数
params = {'id': id, 'menu_id': menu_id, 'action': action_id,
'model': 'quality.check',
'view_type': 'form'}
# 拼接查询参数
query_string = urlencode(params)
# 拼接URL
full_url = url + "/web#" + query_string
return full_url

View File

@@ -3,9 +3,6 @@ import logging
import os import os
import json import json
import base64 import base64
import traceback
from odoo import http, fields, models from odoo import http, fields, models
from odoo.http import request from odoo.http import request
from odoo.addons.sf_base.controllers.controllers import MultiInheritController from odoo.addons.sf_base.controllers.controllers import MultiInheritController
@@ -27,6 +24,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
res = {'status': 1, 'message': '成功'} res = {'status': 1, 'message': '成功'}
datas = request.httprequest.data datas = request.httprequest.data
model_id = None model_id = None
part_number = None
ret = json.loads(datas) ret = json.loads(datas)
ret = json.loads(ret['result']) ret = json.loads(ret['result'])
logging.info('下发编程单:%s' % ret) logging.info('下发编程单:%s' % ret)
@@ -62,7 +60,6 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
res['message'] = '编程单号为%s的CNC程序文件从FTP拉取失败' % (ret['programming_no']) res['message'] = '编程单号为%s的CNC程序文件从FTP拉取失败' % (ret['programming_no'])
return json.JSONEncoder().encode(res) return json.JSONEncoder().encode(res)
for production in productions: for production in productions:
model_id = production.product_id.model_id # 一个编程单的制造订单对应同一个模型
production.write({'programming_state': '已编程', 'work_state': '已编程', 'is_rework': False}) production.write({'programming_state': '已编程', 'work_state': '已编程', 'is_rework': False})
for panel in ret['processing_panel'].split(','): for panel in ret['processing_panel'].split(','):
# 查询状态为进行中且工序类型为CNC加工的工单 # 查询状态为进行中且工序类型为CNC加工的工单
@@ -79,6 +76,10 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
cnc_workorder_has.write( cnc_workorder_has.write(
{'cnc_ids': cnc_workorder_has.cnc_ids.sudo()._json_cnc_processing(panel, ret), {'cnc_ids': cnc_workorder_has.cnc_ids.sudo()._json_cnc_processing(panel, ret),
'cmm_ids': cnc_workorder_has.cmm_ids.sudo()._json_cmm_program(panel, ret)}) 'cmm_ids': cnc_workorder_has.cmm_ids.sudo()._json_cmm_program(panel, ret)})
# 一个编程单的制造订单对应同一个模型
model_id = productions[0].product_id.model_id
part_number = productions[0].product_id.part_number
for panel in ret['processing_panel'].split(','): for panel in ret['processing_panel'].split(','):
# 查询状态为进行中且工序类型为CNC加工的工单 # 查询状态为进行中且工序类型为CNC加工的工单
cnc_workorder = productions.workorder_ids.filtered( cnc_workorder = productions.workorder_ids.filtered(
@@ -98,7 +99,12 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
logging.info('panel_file_path:%s' % panel_file_path) logging.info('panel_file_path:%s' % panel_file_path)
# 向编程单中添加二维码 # 向编程单中添加二维码
request.env['printing.utils'].add_qr_code_to_pdf(panel_file_path, model_id, "模型ID%s" % model_id) request.env['printing.utils'].add_qr_code_to_pdf(
panel_file_path,
model_id,
"模型ID%s" % model_id,
"零件图号:%s" % part_number if part_number else None
)
cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())}) cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
pre_workorder = productions.workorder_ids.filtered( pre_workorder = productions.workorder_ids.filtered(
lambda ap: ap.routing_type in ['装夹预调', '人工线下加工'] and ap.state not in ['done', 'rework' lambda ap: ap.routing_type in ['装夹预调', '人工线下加工'] and ap.state not in ['done', 'rework'
@@ -137,94 +143,114 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
for production in productions: for production in productions:
logging.info('production====:%s' % production.name) logging.info('production====:%s' % production.name)
record_ids_obj = production.programming_record_ids.filtered( record_ids_obj = production.programming_record_ids.filtered(
lambda r: r.current_programming_count == ret['reprogramming_num']) lambda r: r.send_time is False)
logging.info('record_ids_obj====:%s' % record_ids_obj) logging.info('record_ids_obj====:%s' % record_ids_obj)
if record_ids_obj: if record_ids_obj:
record_ids_obj = record_ids_obj.sorted('id')[0]
logging.info('record_ids_obj.reason====:%s' % record_ids_obj.reason) logging.info('record_ids_obj.reason====:%s' % record_ids_obj.reason)
record_ids_obj.write( record_ids_obj.write({
{'send_time': ret['send_time'], 'target_production_id': productions_reprogram}) 'current_programming_count': ret['reprogramming_num'],
'send_time': ret['send_time'],
'target_production_id': productions_reprogram,
'programming_method': ret['programme_way']
})
logging.info('已更新编程记录:%s' % record_ids_obj) logging.info('已更新编程记录:%s' % record_ids_obj)
correct_record_ids_obj = record_ids_obj correct_record_ids_obj = record_ids_obj
correct_production_id = production.id correct_production_id = production.id
if ret['reprogramming_num'] == 0: # 更新重新编程记录
logging.info('首次下发')
production.programming_record_ids.create({
'number': 1,
'production_id': production.id,
'reason': '首次下发',
'programming_method': ret['programme_way'],
'current_programming_count': ret['reprogramming_num'],
'target_production_id': productions_reprogram,
'apply_time': False,
'send_time': ret['send_time'],
})
logging.info('已创建首次下发的编程记录:%s' % production.name)
elif ret['reset_flag']:
logging.info('重置状态')
production.programming_record_ids.create({
'number': len(production.programming_record_ids) + 1,
'production_id': production.id,
'reason': '重置状态',
'programming_method': ret['programme_way'],
'current_programming_count': ret['reprogramming_num'],
'target_production_id': productions_reprogram,
'apply_time': False,
'send_time': ret['send_time'],
})
logging.info('已创建重置状态的编程记录:%s' % production.name)
elif ret['manufacturing_type'] == 'rework':
logging.info('返工')
rework_record_ids_obj = production.programming_record_ids.create({
'number': len(production.programming_record_ids) + 1,
'production_id': production.id,
'reason': '返工',
'programming_method': ret['programme_way'],
'current_programming_count': ret['reprogramming_num'],
'target_production_id': productions_reprogram,
'apply_time': ret['trigger_time'],
'send_time': ret['send_time'],
})
logging.info('已创建返工的编程记录:%s' % production.name)
logging.info('rework_record_ids_obj====:%s' % rework_record_ids_obj)
# rework_production_id = production.id
# logging.info('rework_production_id====:%s' % rework_production_id)
elif ret['manufacturing_type'] == 'scrap':
production.programming_record_ids.create({
'number': len(production.programming_record_ids) + 1,
'production_id': production.id,
'reason': '报废',
'programming_method': ret['programme_way'],
'current_programming_count': ret['reprogramming_num'],
'target_production_id': productions_reprogram,
'apply_time': ret['trigger_time'],
'send_time': ret['send_time'],
})
elif ret['manufacturing_type'] == 'invalid_tool_rework':
logging.info('无效功能刀具')
production.programming_record_ids.create({
'number': len(production.programming_record_ids) + 1,
'production_id': production.id,
'reason': '无效功能刀具',
'programming_method': ret['programme_way'],
'current_programming_count': ret['reprogramming_num'],
'target_production_id': productions_reprogram,
'apply_time': ret['trigger_time'],
'send_time': ret['send_time'],
})
logging.info('已创建无效功能刀具的编程记录:%s' % production.name)
elif ret['reprogramming_reason']:
production.programming_record_ids.create({
'number': len(production.programming_record_ids) + 1,
'production_id': production.id,
'reason': ret['reprogramming_reason'],
'programming_method': ret['programme_way'],
'current_programming_count': ret['reprogramming_num'],
'target_production_id': productions_reprogram,
'apply_time': ret['trigger_time'],
'send_time': ret['send_time'],
})
else: else:
logging.info('无对应状态,不需更新编程记录') if ret['reset_flag']:
logging.info('重置状态')
production.programming_record_ids.create({
'number': len(production.programming_record_ids) + 1,
'production_id': production.id,
'reason': '重置状态',
'programming_method': ret['programme_way'],
'current_programming_count': ret['reprogramming_num'],
'target_production_id': productions_reprogram,
'apply_time': False,
'send_time': ret['send_time'],
})
else:
logging.info('无对应状态,不需更新编程记录')
# if ret['reprogramming_num'] == 0:
# logging.info('首次下发')
# production.programming_record_ids.create({
# 'number': 1,
# 'production_id': production.id,
# 'reason': '首次下发',
# 'programming_method': ret['programme_way'],
# 'current_programming_count': ret['reprogramming_num'],
# 'target_production_id': productions_reprogram,
# 'apply_time': False,
# 'send_time': ret['send_time'],
# })
# logging.info('已创建首次下发的编程记录:%s' % production.name)
# elif ret['reset_flag']:
# logging.info('重置状态')
# production.programming_record_ids.create({
# 'number': len(production.programming_record_ids) + 1,
# 'production_id': production.id,
# 'reason': '重置状态',
# 'programming_method': ret['programme_way'],
# 'current_programming_count': ret['reprogramming_num'],
# 'target_production_id': productions_reprogram,
# 'apply_time': False,
# 'send_time': ret['send_time'],
# })
# logging.info('已创建重置状态的编程记录:%s' % production.name)
# elif ret['manufacturing_type'] == 'rework':
# logging.info('返工')
# rework_record_ids_obj = production.programming_record_ids.create({
# 'number': len(production.programming_record_ids) + 1,
# 'production_id': production.id,
# 'reason': '返工',
# 'programming_method': ret['programme_way'],
# 'current_programming_count': ret['reprogramming_num'],
# 'target_production_id': productions_reprogram,
# 'apply_time': ret['trigger_time'],
# 'send_time': ret['send_time'],
# })
# logging.info('已创建返工的编程记录:%s' % production.name)
# logging.info('rework_record_ids_obj====:%s' % rework_record_ids_obj)
# # rework_production_id = production.id
# # logging.info('rework_production_id====:%s' % rework_production_id)
# elif ret['manufacturing_type'] == 'scrap':
# production.programming_record_ids.create({
# 'number': len(production.programming_record_ids) + 1,
# 'production_id': production.id,
# 'reason': '报废',
# 'programming_method': ret['programme_way'],
# 'current_programming_count': ret['reprogramming_num'],
# 'target_production_id': productions_reprogram,
# 'apply_time': ret['trigger_time'],
# 'send_time': ret['send_time'],
# })
# elif ret['manufacturing_type'] == 'invalid_tool_rework':
# logging.info('无效功能刀具')
# production.programming_record_ids.create({
# 'number': len(production.programming_record_ids) + 1,
# 'production_id': production.id,
# 'reason': '无效功能刀具',
# 'programming_method': ret['programme_way'],
# 'current_programming_count': ret['reprogramming_num'],
# 'target_production_id': productions_reprogram,
# 'apply_time': ret['trigger_time'],
# 'send_time': ret['send_time'],
# })
# logging.info('已创建无效功能刀具的编程记录:%s' % production.name)
# elif ret['reprogramming_reason']:
# production.programming_record_ids.create({
# 'number': len(production.programming_record_ids) + 1,
# 'production_id': production.id,
# 'reason': ret['reprogramming_reason'],
# 'programming_method': ret['programme_way'],
# 'current_programming_count': ret['reprogramming_num'],
# 'target_production_id': productions_reprogram,
# 'apply_time': ret['trigger_time'],
# 'send_time': ret['send_time'],
# })
for production in productions: for production in productions:
logging.info('production====:%s' % production.name) logging.info('production====:%s' % production.name)
@@ -243,6 +269,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
'current_programming_count': correct_record_ids_obj.current_programming_count, 'current_programming_count': correct_record_ids_obj.current_programming_count,
'target_production_id': correct_record_ids_obj.target_production_id, 'target_production_id': correct_record_ids_obj.target_production_id,
'apply_time': correct_record_ids_obj.apply_time, 'apply_time': correct_record_ids_obj.apply_time,
'apply_uid': correct_record_ids_obj.apply_uid.id,
'send_time': correct_record_ids_obj.send_time, 'send_time': correct_record_ids_obj.send_time,
}) })
logging.info('已创建正确的制造订单的编程记录:%s' % production.name) logging.info('已创建正确的制造订单的编程记录:%s' % production.name)
@@ -276,8 +303,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
except Exception as e: except Exception as e:
res = {'status': -1, 'message': '系统解析失败'} res = {'status': -1, 'message': '系统解析失败'}
request.cr.rollback() request.cr.rollback()
traceback_error = traceback.format_exc() logging.info('get_cnc_processing_create error:%s' % e)
logging.error("get_cnc_processing_create error:%s" % traceback_error)
return json.JSONEncoder().encode(res) return json.JSONEncoder().encode(res)

View File

@@ -1149,7 +1149,7 @@ class sfProductionProcessParameter(models.Model):
'processing_mm': item['processing_mm'], 'processing_mm': item['processing_mm'],
'gain_way':'外协', 'gain_way':'外协',
}) })
production_process_parameter.create_service_product() # production_process_parameter.create_service_product()
else: else:
production_process_parameter.gain_way = '外协' production_process_parameter.gain_way = '外协'
production_process_parameter.name = item['name'] production_process_parameter.name = item['name']
@@ -1161,9 +1161,9 @@ class sfProductionProcessParameter(models.Model):
[('materials_no', 'in', item['materials_model_ids_codes'])]) [('materials_no', 'in', item['materials_model_ids_codes'])])
production_process_parameter.active = item['active'] production_process_parameter.active = item['active']
production_process_parameter.processing_mm = item['processing_mm'] production_process_parameter.processing_mm = item['processing_mm']
if not production_process_parameter.outsourced_service_products: # if not production_process_parameter.outsourced_service_products:
production_process_parameter.create_service_product() # production_process_parameter.create_service_product()
production_process_parameter.create_work_center() # production_process_parameter.create_work_center()
else: else:
raise ValidationError("表面工艺可选参数认证未通过") raise ValidationError("表面工艺可选参数认证未通过")

View File

@@ -15,6 +15,7 @@ class sf_production_plan(models.Model):
# _order = 'state desc, write_date desc' # _order = 'state desc, write_date desc'
part_name = fields.Char('零件名称', related='product_id.part_name', readonly=True) part_name = fields.Char('零件名称', related='product_id.part_name', readonly=True)
part_number = fields.Char('零件图号', related='product_id.part_number', readonly=True) part_number = fields.Char('零件图号', related='product_id.part_number', readonly=True)
model_id = fields.Char('模型ID', related='product_id.model_id')
state = fields.Selection([ state = fields.Selection([
('draft', '待排程'), ('draft', '待排程'),
('done', '已排程'), ('done', '已排程'),

View File

@@ -170,6 +170,7 @@
<field name="part_number"/> <field name="part_number"/>
<field name="order_deadline" filter_domain="[('order_deadline', 'ilike', self)]"/> <field name="order_deadline" filter_domain="[('order_deadline', 'ilike', self)]"/>
<field name="production_line_id"/> <field name="production_line_id"/>
<field name="model_id"/>
<filter string="待排程" name="draft" domain="[('state','=','draft')]"/> <filter string="待排程" name="draft" domain="[('state','=','draft')]"/>
<filter string="已排程" name="done" domain="[('state','=','done')]"/> <filter string="已排程" name="done" domain="[('state','=','done')]"/>
<filter string="加工中" name="processing" domain="[('state','=','processing')]"/> <filter string="加工中" name="processing" domain="[('state','=','processing')]"/>

View File

@@ -13,7 +13,7 @@
'author': 'jikimo', 'author': 'jikimo',
'website': 'https://sf.cs.jikimo.com', 'website': 'https://sf.cs.jikimo.com',
# 此处依赖sf_manufacturing是因为我要重写其中的一个字段operation_id的string故需要sf_manufacturing先安装 # 此处依赖sf_manufacturing是因为我要重写其中的一个字段operation_id的string故需要sf_manufacturing先安装
'depends': ['quality_control', 'web_widget_model_viewer', 'sf_manufacturing','jikimo_attachment_viewer'], 'depends': ['quality_control', 'web_widget_model_viewer', 'sf_manufacturing','jikimo_attachment_viewer', 'jikimo_confirm_dialog'],
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'data/check_standards.xml', 'data/check_standards.xml',

View File

@@ -5,7 +5,7 @@ from odoo import fields, models, api
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from datetime import datetime from datetime import datetime
from odoo.addons.sf_base.commons.common import Common from odoo.addons.sf_base.commons.common import Common
from odoo.tools import float_round from odoo.tools import float_round, float_compare
class QualityCheck(models.Model): class QualityCheck(models.Model):
@@ -208,4 +208,96 @@ class QualityCheck(models.Model):
'title': '警告', 'title': '警告',
'message': '不合格数量不能超过已检数量' 'message': '不合格数量不能超过已检数量'
} }
} }
total_qty_readonly = fields.Boolean(compute='_compute_total_qty_readonly', store=True)
def _compute_total_qty_readonly(self):
report_test_type_id = self.env.ref('sf_quality.test_type_factory_inspection').id
for record in self:
if (record.measure_on != 'move_line' or record.workorder_id is False) and record.point_id.test_type_id.id != report_test_type_id:
record.total_qty_readonly = True
else:
record.total_qty_readonly = False
def preview_doc(self):
"""预览出厂检验报告"""
self.ensure_one()
picking_qty = sum(self.picking_id.move_ids.filtered(lambda m: m.product_id == self.product_id).mapped('product_uom_qty'))
if not self._check_total_qty(picking_qty) and self.quality_state in ['waiting', 'none']:
return {
'type': 'ir.actions.client',
'tag': 'jikimo_confirm_dialog',
'params': {
'active_id': self.id,
'message': f"拣货调拨单号{self.picking_id.name}需求数量为{picking_qty},当前质量检查单产品数量为{self.total_qty},数量不一致,是否确认继续?",
'next_model': self._name,
'next_method': 'preview_doc_confirm',
'context': self.env.context
}
}
action = self.env.ref("sf_quality.action_report_quality_inspection_preview").read()[0]
return action
def preview_doc_confirm(self):
self.ensure_one()
action = self.env.ref("sf_quality.action_report_quality_inspection_preview").read()[0]
action['context'] = {
'active_id': self.id,
'active_ids': [self.id]
}
return action
def _check_total_qty(self, compare_qty):
"""
检查质量检查单的总数量是否匹配
"""
self.ensure_one()
return float_compare(float(self.total_qty), compare_qty, self.picking_id.product_id.uom_id.rounding) == 0
def preview_do_publish(self):
self.ensure_one()
self._check_part_number()
self._check_measure_line()
self._check_check_qty_and_total_qty()
picking_qty = sum(self.picking_id.move_ids.filtered(lambda m: m.product_id == self.product_id).mapped('product_uom_qty'))
if not self._check_total_qty(picking_qty):
return {
'type': 'ir.actions.client',
'tag': 'jikimo_confirm_dialog',
'params': {
'active_id': self.id,
'message': f"拣货调拨单号{self.picking_id.name}需求数量为{picking_qty},当前质量检查单产品数量为{self.total_qty},数量不一致,是否确认继续?",
'next_model': self._name,
'next_method': 'preview_do_publish_confirm',
'context': self.env.context,
}
}
else:
return self.do_publish()
def preview_do_publish_confirm(self):
self.ensure_one()
return {
'name': '发布确认',
'type': 'ir.actions.act_window',
'res_model': 'quality.check.publish.wizard',
'view_mode': 'form',
'views': [[False, 'form']],
'target': 'new',
'context': {
'default_check_id': self.id,
'default_product_name': self.product_id.name,
'default_total_qty': self.total_qty,
'default_check_qty': self.check_qty,
'default_measure_count': self.column_nums,
'default_item_count': len(self.measure_line_ids),
'default_old_report_name': self.old_report_name,
'default_publish_status': self.publish_status,
'is_web_request': True
}
}

View File

@@ -73,47 +73,50 @@
<xpath expr="//header" position="inside"> <xpath expr="//header" position="inside">
<field name="is_out_check" invisible="1"/> <field name="is_out_check" invisible="1"/>
<field name="publish_status" invisible="1"/> <field name="publish_status" invisible="1"/>
<button name="%(sf_quality.action_report_quality_inspection_preview)d" <button name="preview_doc"
string="预览" string="预览"
type="action" type="object"
class="oe_highlight" attrs="{'invisible': [('is_out_check', '=', False)]}"/> class="oe_highlight" attrs="{'invisible': [('is_out_check', '=', False)]}"/>
<!-- --><!-- 如果还需要打印按钮 --> <!-- --><!-- 如果还需要打印按钮 -->
<!-- <button name="%(sf_quality.action_report_quality_inspection)d" --> <!-- <button name="%(sf_quality.action_report_quality_inspection)d" -->
<!-- string="打印报告" --> <!-- string="打印报告" -->
<!-- type="action"/> --> <!-- type="action"/> -->
<!-- <button name="do_preview" string="预览" type="object" class="btn-primary" attrs="{'invisible': [('is_out_check', '=', False)]}"/> --> <!-- <button name="do_preview" string="预览" type="object" class="btn-primary" attrs="{'invisible': [('is_out_check', '=', False)]}"/> -->
<button name="do_publish" string="发布" type="object" class="btn-primary" attrs="{'invisible': ['|', ('is_out_check', '=', False), ('publish_status', '!=', 'draft')]}"/> <button name="preview_do_publish" string="发布" type="object" class="btn-primary" attrs="{'invisible': ['|', ('is_out_check', '=', False), ('publish_status', '!=', 'draft')]}"/>
<!-- <button name="get_report_url" string="ceshi" type="object" class="btn-primary"/> --> <!-- <button name="get_report_url" string="ceshi" type="object" class="btn-primary"/> -->
<!-- <button name="upload_factory_report" string="upload_factory_report" type="object" class="btn-primary"/> --> <!-- <button name="upload_factory_report" string="upload_factory_report" type="object" class="btn-primary"/> -->
<button name="do_cancel_publish" string="取消发布" type="object" class="btn-primary" confirm="确定取消发布吗?" attrs="{'invisible': ['|',('is_out_check', '=', False), ('publish_status', '!=', 'published')]}"/> <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')]}"/> <button name="preview_do_publish" string="重新发布" type="object" class="btn-primary" attrs="{'invisible': ['|', ('is_out_check', '=', False), ('publish_status', '!=', 'canceled')]}"/>
</xpath>
<xpath expr="//field[@name='total_qty']" position="before">
<field name="total_qty_readonly" invisible="1"/>
</xpath> </xpath>
<xpath expr="//field[@name='total_qty']" position="attributes"> <xpath expr="//field[@name='total_qty']" position="attributes">
<attribute name="attrs">{ <attribute name="attrs">{
'invisible': ['&amp;', '|', ('measure_on', '!=', 'product'), ('is_out_check', '=', False), '|', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False)], 'invisible': ['&amp;', '|', ('measure_on', '!=', 'product'), ('is_out_check', '=', False), '|', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False)],
'readonly': ['|', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False)], 'readonly': [('total_qty_readonly', '=', True)],
'on_change': ['|', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False)] 'on_change': ['|', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False)]
}</attribute> }</attribute>
</xpath> </xpath>
<xpath expr="//field[@name='total_qty']" position="after"> <xpath expr="//field[@name='total_qty']" position="after">
<label for="workorder_qty_to_test" <label for="workorder_qty_to_test"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/> attrs="{'invisible': ['|', ('measure_on', '!=', 'move_line'), '&amp;', ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/>
<div class="o_row" <div class="o_row"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"> attrs="{'invisible': ['|', ('measure_on', '!=', 'move_line'), '&amp;', ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}">
<field name="workorder_qty_to_test" attrs="{'readonly': 0, 'on_chnage': 1}"/> <field name="workorder_qty_to_test" attrs="{'readonly': 0, 'on_chnage': 1}"/>
<field name="uom_id"/> <field name="uom_id"/>
</div> </div>
<label for="workorder_qty_tested" <label for="workorder_qty_tested"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/> attrs="{'invisible': ['|', ('measure_on', '!=', 'move_line'), '&amp;', ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/>
<div class="o_row" <div class="o_row"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"> attrs="{'invisible': ['|', ('measure_on', '!=', 'move_line'), '&amp;', ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}">
<field name="workorder_qty_tested" attrs="{'readonly': [('quality_state', '!=', 'none')], 'on_chnage': 1}"/> <field name="workorder_qty_tested" attrs="{'readonly': [('quality_state', '!=', 'none')], 'on_chnage': 1}"/>
<field name="uom_id"/> <field name="uom_id"/>
</div> </div>
<label for="workorder_qty_test_failed" <label for="workorder_qty_test_failed"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/> attrs="{'invisible': ['|', ('measure_on', '!=', 'move_line'), '&amp;', ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/>
<div class="o_row" <div class="o_row"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"> attrs="{'invisible': ['|', ('measure_on', '!=', 'move_line'), '&amp;', ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}">
<field name="workorder_qty_test_failed" attrs="{'readonly': [('quality_state', '!=', 'none')], 'on_chnage': 1}"/> <field name="workorder_qty_test_failed" attrs="{'readonly': [('quality_state', '!=', 'none')], 'on_chnage': 1}"/>
<field name="uom_id"/> <field name="uom_id"/>
</div> </div>

View File

@@ -17,6 +17,7 @@
'wizard/sale_order_wizard_views.xml', 'wizard/sale_order_wizard_views.xml',
'wizard/purchase_order_wizard_views.xml', 'wizard/purchase_order_wizard_views.xml',
'data/cron_data.xml', 'data/cron_data.xml',
'data/documents_data.xml',
'views/sale_team.xml', 'views/sale_team.xml',
'views/sale_order_view.xml', 'views/sale_order_view.xml',
'views/res_partner_view.xml', 'views/res_partner_view.xml',

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- 创建采购合同文件夹 -->
<record id="documents_sales_contracts_folder" model="documents.folder">
<field name="name">销售合同</field>
<field name="description">存放销售合同相关文件</field>
<field name="sequence">8</field>
</record>
<record id="documents_sales_contracts_folder_1" model="documents.folder">
<field name="name">下单凭证</field>
<field name="parent_folder_id" ref="documents_sales_contracts_folder"/>
</record>
</data>
</odoo>

View File

@@ -63,6 +63,10 @@ class ReSaleOrder(models.Model):
model_display_version = fields.Char('模型展示版本', default="v1") model_display_version = fields.Char('模型展示版本', default="v1")
contract_document_id = fields.Many2one('documents.document', string='合同文件')
contract_file = fields.Binary(related='contract_document_id.datas', string='合同文件内容')
contract_file_name = fields.Char(related='contract_document_id.attachment_id.name', string='文件名')
# 业务平台分配工厂后在智能工厂先创建销售订单 # 业务平台分配工厂后在智能工厂先创建销售订单
def sale_order_create(self, company_id, delivery_name, delivery_telephone, delivery_address, def sale_order_create(self, company_id, delivery_name, delivery_telephone, delivery_address,
deadline_of_delivery, payments_way, pay_way, order_number, state='sale', deadline_of_delivery, payments_way, pay_way, order_number, state='sale',
@@ -194,18 +198,15 @@ class ReSaleOrder(models.Model):
@api.depends('order_line.purchase_line_ids.order_id') @api.depends('order_line.purchase_line_ids.order_id')
def _compute_purchase_order_count(self): def _compute_purchase_order_count(self):
for order in self: for order in self:
order.purchase_order_count = len(order._get_purchase_orders().filtered( order.purchase_order_count = len(order._get_sale_to_purchase('outsourcing'))
lambda po: po.purchase_type not in ['outsourcing'])) order.consignment_purchase_order_count = len(order._get_sale_to_purchase_1('outsourcing'))
order.consignment_purchase_order_count = len(order._get_purchase_orders().filtered(
lambda po: po.purchase_type in ['outsourcing']))
def action_view_purchase_orders(self): def action_view_purchase_orders(self):
""" """
采购 采购
""" """
self.ensure_one() self.ensure_one()
purchase_order_ids = self._get_purchase_orders().filtered( purchase_order_ids = self._get_sale_to_purchase('outsourcing')
lambda po: po.purchase_type not in ['outsourcing']).ids
action = { action = {
'res_model': 'purchase.order', 'res_model': 'purchase.order',
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
@@ -223,13 +224,20 @@ class ReSaleOrder(models.Model):
}) })
return action return action
def _get_sale_to_purchase(self, purchase_type):
"""查询满足条件的采购订单"""
purchase_order_ids = self._get_purchase_orders().filtered(
lambda po: po.purchase_type not in ['outsourcing']).ids
order_ids = self.env['purchase.order'].sudo().search(
[('origin', '=', self.name), ('purchase_type', '!=', purchase_type)]).ids
return list(set(purchase_order_ids) | set(order_ids))
def action_view_consignment_purchase_orders(self): def action_view_consignment_purchase_orders(self):
""" """
委外加工 委外加工
""" """
self.ensure_one() self.ensure_one()
outsourcing_purchase_order_ids = self._get_purchase_orders().filtered( outsourcing_purchase_order_ids = self._get_sale_to_purchase_1('outsourcing')
lambda po: po.purchase_type in ['outsourcing']).ids
action = { action = {
'res_model': 'purchase.order', 'res_model': 'purchase.order',
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
@@ -247,6 +255,14 @@ class ReSaleOrder(models.Model):
}) })
return action return action
def _get_sale_to_purchase_1(self, purchase_type):
"""查询满足条件的采购订单"""
purchase_order_ids = self._get_purchase_orders().filtered(
lambda po: po.purchase_type == purchase_type).ids
order_ids = self.env['purchase.order'].sudo().search(
[('origin', '=', self.name), ('purchase_type', '=', purchase_type)]).ids
return list(set(purchase_order_ids) | set(order_ids))
class ResaleOrderLine(models.Model): class ResaleOrderLine(models.Model):
_inherit = 'sale.order.line' _inherit = 'sale.order.line'
@@ -346,10 +362,10 @@ class RePurchaseOrder(models.Model):
if purchase.order_line[0].product_id.categ_id.name == '坯料': if purchase.order_line[0].product_id.categ_id.name == '坯料':
if purchase.order_line[0].product_id.materials_type_id.gain_way == '外协': if purchase.order_line[0].product_id.materials_type_id.gain_way == '外协':
purchase.purchase_type = 'outsourcing' purchase.purchase_type = 'outsourcing'
request_lines = self.order_line.mapped('purchase_request_lines') # request_lines = self.order_line.mapped('purchase_request_lines')
# 检查是否存在 is_subcontract 为 True 的行 # # 检查是否存在 is_subcontract 为 True 的行
if any(line.is_subcontract for line in request_lines): # if any(line.is_subcontract for line in request_lines):
purchase.purchase_type = 'consignment' # purchase.purchase_type = 'consignment'
delivery_warning = fields.Selection([('normal', '正常'), ('warning', '预警'), ('overdue', '已逾期')], delivery_warning = fields.Selection([('normal', '正常'), ('warning', '预警'), ('overdue', '已逾期')],
@@ -384,28 +400,28 @@ class RePurchaseOrder(models.Model):
if not line.taxes_id: if not line.taxes_id:
raise UserError('请对【产品】中的【税】进行选择') raise UserError('请对【产品】中的【税】进行选择')
def get_purchase_request(self, consecutive_process_parameters, production): # def get_purchase_request(self, consecutive_process_parameters, production):
result = [] # result = []
for pp in consecutive_process_parameters: # for pp in consecutive_process_parameters:
server_template = self.env['product.template'].search( # server_template = self.env['product.template'].search(
[('server_product_process_parameters_id', '=', pp.surface_technics_parameters_id.id), # [('server_product_process_parameters_id', '=', pp.surface_technics_parameters_id.id),
('detailed_type', '=', 'service')]) # ('detailed_type', '=', 'service')])
# route_ids # # route_ids
result.append({ # result.append({
"product_id": server_template.product_variant_id.id, # "product_id": server_template.product_variant_id.id,
'related_product': production.product_id.id, # 'related_product': production.product_id.id,
"name": production.procurement_group_id.name, # "name": production.procurement_group_id.name,
"date_required": fields.Datetime.now(), # "date_required": fields.Datetime.now(),
"product_uom_id":server_template.uom_id.id, # "product_uom_id":server_template.uom_id.id,
"product_qty": production.product_qty, # "product_qty": production.product_qty,
"request_id": False, # "request_id": False,
"move_dest_ids": False, # "move_dest_ids": False,
"orderpoint_id": False, # "orderpoint_id": False,
'is_subcontract':True, # 'is_subcontract':True,
'group_id':production.procurement_group_id.id, # 'group_id':production.procurement_group_id.id,
'production_name':pp.production_id.name, # 'production_name':pp.production_id.name,
}) # })
return result # return result
def get_purchase_order(self, consecutive_process_parameters, production, product_id_to_production_names): def get_purchase_order(self, consecutive_process_parameters, production, product_id_to_production_names):
for pp in consecutive_process_parameters: for pp in consecutive_process_parameters:

View File

@@ -198,7 +198,20 @@
</div> </div>
<field name="date_order" attrs="{'invisible': [('state', 'in', ['done', 'cancel'])], 'required': True}" nolabel="1"/> <field name="date_order" attrs="{'invisible': [('state', 'in', ['done', 'cancel'])], 'required': True}" nolabel="1"/>
</xpath> </xpath>
<xpath expr="//notebook/page[@name='customer_signature']" position="after">
<page string="合同" name="contract_documents"
attrs="{'invisible': [('contract_document_id', '=', False)]}">
<group>
<group>
<field name="contract_document_id" invisible="1"/>
<field name="contract_file_name" invisible="1"/>
<field name="contract_file"
widget="adaptive_viewer"
filename="contract_file_name"/>
</group>
</group>
</page>
</xpath>
</field> </field>
</record> </record>

View File

@@ -41,6 +41,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/ToolInventory', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/ToolInventory', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('功能刀具清单', requester='中控系统')
def get_functional_tool_inventory_Info(self, **kw): def get_functional_tool_inventory_Info(self, **kw):
""" """
功能刀具清单接口 功能刀具清单接口
@@ -70,6 +71,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/ToolEntity', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/ToolEntity', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('功能刀具', requester='中控系统')
def get_functional_tool_entity_Info(self, **kw): def get_functional_tool_entity_Info(self, **kw):
""" """
功能刀具列表接口 功能刀具列表接口

View File

@@ -150,6 +150,17 @@ class FunctionalCuttingToolEntity(models.Model):
else: else:
# 原刀从线边出库 # 原刀从线边出库
item.tool_in_out_stock_location_1(location_id, tool_room_id) item.tool_in_out_stock_location_1(location_id, tool_room_id)
# 系统中该刀在线边刀架其他位置,需先清除这个位置的刀具信息
shelf_location_id = self.env['sf.shelf.location'].sudo().search([
('product_sn_id', '=', item.barcode_id.id)])
if shelf_location_id:
shelf_location_id.write(
{'product_id': None,
'product_sn_id': None,
'tool_rfid': None,
"tool_name_id": None,
'product_num': 0,
'location_status': '空闲'})
# 新刀入库到线边 # 新刀入库到线边
item.create_stock_move(pre_manufacturing_id, location_id) item.create_stock_move(pre_manufacturing_id, location_id)
item.current_shelf_location_id = location_id.id item.current_shelf_location_id = location_id.id

View File

@@ -121,12 +121,13 @@ class MrsShelfLocationDataSync(models.Model):
tool.tool_in_out_stock_location(location_id) tool.tool_in_out_stock_location(location_id)
if tool: if tool:
location_id.product_sn_id = tool.barcode_id.id location_id.product_sn_id = tool.barcode_id.id
# 修改功能刀具状态 # 修改功能刀具标准状态值和已使用寿命值、功能刀具状态
if item.get('State') == '报警': if 'LifeStd' in item and 'LifeUse' in item:
if tool.functional_tool_status != item.get('State'): tool.sudo().write({
tool.write({ 'max_lifetime_value': item['LifeStd'],
'functional_tool_status': item['State'] 'used_value': item['LifeUse'],
}) 'functional_tool_status': item['State'],
})
else: else:
location_id.product_sn_id = False location_id.product_sn_id = False
if item['RfidCode']: if item['RfidCode']: