Compare commits

...

257 Commits

Author SHA1 Message Date
yuxianghui
b8894609a9 功能刀具寿命到期消息通知 2025-06-12 10:10:13 +08:00
yuxianghui
3f8fd6da62 创建刀具拆解单判断条件优化 2025-06-12 09:40:32 +08:00
yuxianghui
62ead52f00 处理功能刀具预警时没有创建功能刀具预警记录和功能刀具拆解单bug 2025-06-11 17:18:59 +08:00
胡尧
8841d800ea Merge branch 'hotfix/备注为空' into release/release_2.14 2025-06-11 11:28:35 +08:00
guanhuan
920e96ffc6 修复备注为空 2025-06-11 11:19:31 +08:00
yuxianghui
d52f5fa841 修复取消订单时报错bug 2025-06-11 11:15:03 +08:00
胡尧
e0559e9887 Merge branch 'develop' into release/release_2.14 2025-06-10 16:33:57 +08:00
管欢
39a25bb6c8 Accept Merge Request #2180: (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/2180
2025-06-10 16:33:37 +08:00
禹翔辉
e129c08426 Accept Merge Request #2179: (feature/bfm下单接口优化 -> 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/2179
2025-06-10 15:28:17 +08:00
yuxianghui
c6b47bd68d 销售订单新增合同日期字段 2025-06-10 15:26:32 +08:00
guanhuan
126d60f8d7 Merge branch 'refs/heads/feature/搜索优化' into release/release_2.14 2025-06-10 10:45:52 +08:00
guanhuan
4225a8fe1b 采购申请单完成操作提示调整 2025-06-10 10:45:06 +08:00
guanhuan
a13a79f41f Merge branch 'refs/heads/feature/搜索优化' into release/release_2.14 2025-06-09 17:56:50 +08:00
guanhuan
a828c823dd 采购申请单完成操作修改 2025-06-09 17:55:34 +08:00
guanhuan
9cf2bac9c6 修复返工问题 2025-06-09 17:39:55 +08:00
胡尧
484fab85be Accept Merge Request #2176: (feature/6694 -> develop)
Merge Request: 修改出厂检验报告预览数据

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2176
2025-06-09 09:09:06 +08:00
胡尧
6ed5de6400 修改出厂检验报告预览数据 2025-06-09 09:08:26 +08:00
胡尧
2449b92bc8 Accept Merge Request #2175: (feature/6694 -> develop)
Merge Request: 修复点击智能按钮点击的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2175?initial=true
2025-06-06 09:34:15 +08:00
胡尧
03ec94d223 修复点击智能按钮点击的问题 2025-06-06 09:33:23 +08:00
胡尧
d26e6edd31 Accept Merge Request #2174: (feature/6694 -> develop)
Merge Request: 修改制造订单的采购申请只能按钮判断规则

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2174?initial=true
2025-06-06 09:17:06 +08:00
胡尧
2ea24f2049 修改制造订单的采购申请只能按钮判断规则 2025-06-06 09:16:41 +08:00
胡尧
b1a04f8f44 Accept Merge Request #2173: (feature/6694 -> develop)
Merge Request: 在出厂检验报告验证数量时,增加产品的匹配

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2173?initial=true
2025-06-06 08:37:58 +08:00
胡尧
0b5415dc47 在出厂检验报告验证数量时,增加产品的匹配 2025-06-06 08:37:38 +08:00
胡尧
59569806e6 Accept Merge Request #2172: (feature/6694 -> develop)
Merge Request: 修复二次弹窗不能刷新页面的问题,但是js方法还需要修改

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2172
2025-06-06 08:29:51 +08:00
胡尧
4e1be6f4d5 修复二次弹窗不能刷新页面的问题,但是js方法还需要修改 2025-06-06 08:29:25 +08:00
禹翔辉
cdf8fbb12a Accept Merge Request #2171: (feature/销售合同优化 -> develop)
Merge Request: 1、销售订单添加合同编号字段,bfm下单接口添加合同编号同步;2、优化解除装夹工单完工时解绑rfid逻辑,优化工单返工时新工单绑定rfid逻辑

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2171
2025-06-05 17:03:44 +08:00
yuxianghui
5d0f094da7 1 2025-06-05 17:03:06 +08:00
yuxianghui
95cb5251dc 1、销售订单添加合同编号字段,bfm下单接口添加合同编号同步;2、优化解除装夹工单完工时解绑rfid逻辑,优化工单返工时新工单绑定rfid逻辑 2025-06-05 16:59:33 +08:00
禹翔辉
c8fe7504c7 Accept Merge Request #2170: (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/2170
2025-06-05 15:35:03 +08:00
yuxianghui
222efc57c2 返工优化 2025-06-05 15:29:14 +08:00
禹翔辉
735d5c659d Accept Merge Request #2169: (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/2169
2025-06-05 14:53:53 +08:00
yuxianghui
50d188b737 处理自动化产线的确认工艺路线报错 2025-06-05 14:49:09 +08:00
禹翔辉
af3ea0f702 Accept Merge Request #2168: (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/2168
2025-06-05 13:59:35 +08:00
yuxianghui
8bf3b68cee 处理批量修改采购申请报错 2025-06-05 13:52:25 +08:00
禹翔辉
2766bc7d34 Accept Merge Request #2167: (feature/销售合同优化 -> develop)
Merge Request: 返工装夹预调工单时,清除同一个面工单的rfid,并且保留返工工单的rfid记录

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2167
2025-06-05 11:20:09 +08:00
yuxianghui
1082384d00 清除返工工单的rfid,并保留记录 2025-06-05 11:18:47 +08:00
yuxianghui
25aab1576d 返工装夹预调工单时,清除同一个面工单的rfid,并且保留返工工单的rfid记录 2025-06-05 11:11:54 +08:00
禹翔辉
87891b45ef Accept Merge Request #2165: (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/2165
2025-06-05 10:23:23 +08:00
胡尧
b2cfdd8d78 Accept Merge Request #2166: (feature/6694 -> develop)
Merge Request: 修改质检单字段显示

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2166?initial=true
2025-06-05 10:21:11 +08:00
胡尧
540b7bcbea 修改质检单字段显示 2025-06-05 10:19:58 +08:00
yuxianghui
c6cb1d367d 处理返工报错 2025-06-05 10:17:39 +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
胡尧
cab6b6fa2a 解决代码问题 2025-05-12 14:46:15 +08:00
胡尧
35bf954529 增加中控接口调用日志记录 2025-05-12 11:50:35 +08:00
胡尧
ceb38aa483 Accept Merge Request #2106: (feature/tool_standard_library_process -> develop)
Merge Request: 工艺外协代码

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2069?initial=true
2025-04-25 16:40:24 +08:00
胡尧
a7ab8679f4 调整字体大小 2025-04-25 16:39:51 +08:00
胡尧
ca9a91e30a 修改程序单二维码下方文字 2025-04-25 16:07:34 +08:00
胡尧
314d738412 接口授权 2025-04-25 15:59:11 +08:00
91 changed files with 2209 additions and 894 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

@@ -3,8 +3,12 @@ import qrcode
from reportlab.pdfgen import canvas from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4 from reportlab.lib.pagesizes import A4
from PIL import Image from PIL import Image
import logging
from reportlab.lib.utils import ImageReader from reportlab.lib.utils import ImageReader
from odoo import models, fields, api from odoo import models, fields, api
import base64
_logger = logging.getLogger(__name__)
class JikimoPrinting(models.AbstractModel): class JikimoPrinting(models.AbstractModel):
_name = 'jikimo.printing' _name = 'jikimo.printing'
@@ -13,6 +17,11 @@ class JikimoPrinting(models.AbstractModel):
""" """
打印二维码 打印二维码
""" """
printer = self.env['printing.printer'].get_default()
if not printer:
_logger.error("未找到默认打印机")
return False
# 生成二维码 # 生成二维码
qr = qrcode.QRCode(version=1, box_size=10, border=5) qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(data) qr.add_data(data)
@@ -41,16 +50,38 @@ class JikimoPrinting(models.AbstractModel):
# 获取PDF内容并打印 # 获取PDF内容并打印
pdf_content = pdf_buffer.getvalue() pdf_content = pdf_buffer.getvalue()
printer = self.env['printing.printer'].get_default() # _logger.info(f"打印内容: {pdf_content}")
printer.print_document(report=None, content=pdf_content, doc_format='pdf') printer.print_document(report=None, content=pdf_content, doc_format='pdf')
# 清理资源 # 清理资源
pdf_buffer.close() pdf_buffer.close()
temp_image.close() temp_image.close()
return True
def print_pdf(self, pdf_data): def print_pdf(self, pdf_data):
""" """
打印PDF 打印PDF
""" """
printer = self.env['printing.printer'].get_default() printer = self.env['printing.printer'].get_default()
printer.print_document(report=None, content = pdf_data, doc_format='pdf') if not printer:
_logger.error("未找到默认打印机")
return False
pdf_data_str = pdf_data.decode('ascii', errors='ignore')
decoded_data = base64.b64decode(pdf_data_str)
# 处理二进制数据
pdf_buffer = BytesIO()
pdf_buffer.write(decoded_data)
pdf_buffer.seek(0)
# 获取PDF内容
pdf_content = pdf_buffer.getvalue()
printer.print_document(report=None, content=pdf_content, doc_format='pdf')
# 清理资源
pdf_buffer.close()
_logger.info("成功打印PDF")
return True

View File

@@ -21,8 +21,6 @@ class MrpWorkorder(models.Model):
# 执行打印 # 执行打印
self.env['jikimo.printing'].print_pdf(pdf_data) self.env['jikimo.printing'].print_pdf(pdf_data)
wo.production_id.product_id.is_print_program = True wo.production_id.product_id.is_print_program = True
_logger.info(f"工单 {wo.name} 的PDF已成功打印")
except Exception as e: except Exception as e:
_logger.error(f"工单 {wo.name} 的PDF打印失败: {str(e)}") _logger.error(f"工单 {wo.name} 的PDF打印失败: {str(e)}")

View File

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

View File

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

View File

@@ -9,18 +9,37 @@ class MrpProduction(models.Model):
@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:
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name), ('is_subcontract', '!=', 'True')]) # if item.product_id.product_tmpl_id.single_manufacturing == True and not item.is_remanufacture:
if pr_ids: # first_order = self.env['mrp.production'].search(
item.pr_mp_count = len(pr_ids) # [('origin', '=', item.origin), ('product_id', '=', item.product_id.id)], limit=1, order='id asc')
else: # pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', first_order.name)])
item.pr_mp_count = 0 # item.pr_mp_count = len(pr_ids)
# else:
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name)])
# item.pr_mp_count = len(pr_ids)
# 由于采购申请合并了所有销售订单行的采购,所以不区分产品
mrp_names = self.env['mrp.production'].search([('origin', '=', item.origin)]).mapped('name')
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'in', mrp_names)])
item.pr_mp_count = len(pr_ids)
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name), ('is_subcontract', '!=', 'True')])
def action_view_pr_mp(self): def action_view_pr_mp(self):
""" """
采购请求 采购请求
""" """
self.ensure_one() self.ensure_one()
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name),('is_subcontract', '!=', True)]) # pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name),('is_subcontract', '!=', True)])
# if self.product_id.product_tmpl_id.single_manufacturing == True and not self.is_remanufacture:
# first_order = self.env['mrp.production'].search(
# [('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',
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',

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,57 @@ 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:
if line_item.state == 'purchase':
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,6 +100,20 @@ 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_count = fields.Integer(string="采购订单数量", compute="_compute_purchase_count", readonly=True)
@api.depends("purchase_lines")
def _compute_purchase_count(self):
for rec in self:
rec.purchase_count = len(rec.mapped("purchase_lines.order_id"))
@api.depends('request_id')
def _compute_purchase_request_count(self):
for order in self:
order.purchase_request_count = len(order.request_id)
@api.depends('origin') @api.depends('origin')
def _compute_supply_method(self): def _compute_supply_method(self):
for prl in self: for prl in self:
@@ -79,7 +146,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)
@@ -98,3 +165,36 @@ class PurchaseRequestLine(models.Model):
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
def _compute_qty_to_buy(self):
for pr in self:
qty_to_buy = sum(pr.mapped("product_qty"))
if pr.purchase_count > 0:
qty_to_buy -= sum(pr.mapped("purchase_lines").filtered(lambda po: po.state != 'cancel').mapped(
"product_qty"))
pr.qty_to_buy = qty_to_buy > 0.0
pr.pending_qty_to_receive = qty_to_buy
def action_view_purchase_request(self):
action = self.env["ir.actions.actions"]._for_xml_id("purchase_request.purchase_request_form_action")
action.update({
'res_id': self.request_id.id,
'views': [[False, 'form']],
})
return action
def action_view_purchase_order(self):
action = self.env["ir.actions.actions"]._for_xml_id("purchase.purchase_rfq")
lines = self.mapped("purchase_lines.order_id")
if len(lines) > 1:
action["domain"] = [("id", "in", lines.ids)]
elif lines:
action["views"] = [
(self.env.ref("purchase.purchase_order_form").id, "form")
]
action["res_id"] = lines.id
origin_context = ast.literal_eval(action['context'])
if 'search_default_draft' in origin_context:
origin_context.pop('search_default_draft')
action['context'] = origin_context
return action

View File

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

@@ -1,4 +1,5 @@
from odoo import api, fields, models from odoo import api, fields, models
from collections import defaultdict
class StockRule(models.Model): class StockRule(models.Model):
@@ -44,13 +45,38 @@ class StockRule(models.Model):
purchase_request_line_model.create(request_line_data) purchase_request_line_model.create(request_line_data)
def _run_buy(self, procurements): def _run_buy(self, procurements):
res = super(StockRule, self)._run_buy(procurements) # 如果补货组相同,并且产品相同,则合并
# 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved procurements_dict = defaultdict()
origins = list(set([procurement[0].origin for procurement in procurements])) for procurement, rule in procurements:
for origin in origins: if (procurement.product_id.id, procurement.values['group_id'], rule.id) not in procurements_dict:
pr_ids = self.env["purchase.request"].sudo().search( procurements_dict[(procurement.product_id.id, procurement.values['group_id'], rule.id)] = {
[('origin', 'like', origin), ('rule_new_add', '=', True), ('state', '=', 'draft')]) 'product_id': procurement.product_id,
if pr_ids: 'product_qty': procurement.product_qty,
pr_ids.write({'need_validation': False}) 'product_uom': procurement.product_uom,
pr_ids.write({"state": "approved", 'need_validation': True, 'rule_new_add': False}) 'location_id': procurement.location_id,
'name': procurement.name,
'origin': procurement.origin,
'company_id': procurement.company_id,
'values': procurement.values,
'rule': rule
}
else:
procurements_dict[(procurement.product_id.id, procurement.values['group_id'], rule.id)]['product_qty'] += procurement.product_qty
procurements_dict[(procurement.product_id.id, procurement.values['group_id'], rule.id)]['values']['move_dest_ids'] |= procurement.values['move_dest_ids']
new_procurements = []
for k, p in procurements_dict.items():
new_procurements.append((
self.env['procurement.group'].Procurement(
product_id=p['product_id'],
product_qty=p['product_qty'],
product_uom=p['product_uom'],
location_id=p['location_id'],
name=p['name'],
origin=p['origin'],
company_id=p['company_id'],
values=p['values']
), p['rule'])
)
res = super(StockRule, self)._run_buy(new_procurements)
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,3 @@
th[data-name=keep_description] {
min-width: 220px;
}

View File

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

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<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

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

View File

@@ -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

@@ -104,9 +104,26 @@ class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
# 去掉合并必须同一采购组的限制 # 去掉合并必须同一采购组的限制
pass pass
def get_items(self, request_line_ids):
request_line_obj = self.env["purchase.request.line"]
items = []
request_lines = request_line_obj.browse(request_line_ids).filtered(lambda line: line.pending_qty_to_receive > 0)
self._check_valid_request_line(request_line_ids)
self.check_group(request_lines)
for line in request_lines:
items.append([0, 0, self._prepare_item(line)])
return items
class PurchaseRequestLineMakePurchaseOrderItem(models.TransientModel): class PurchaseRequestLineMakePurchaseOrderItem(models.TransientModel):
_inherit = "purchase.request.line.make.purchase.order.item" _inherit = "purchase.request.line.make.purchase.order.item"
supply_method = fields.Selection(related='line_id.supply_method', string='供货方式') supply_method = fields.Selection(related='line_id.supply_method', string='供货方式')
wiz_id = fields.Many2one(
comodel_name="purchase.request.line.make.purchase.order",
string="Wizard",
required=False,
ondelete="cascade",
readonly=True,
)

View File

@@ -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

@@ -6,7 +6,7 @@ from odoo.addons.sf_base.decorators.api_log import api_log
class MainController(http.Controller): class MainController(http.Controller):
@http.route('/api/manual_download_program', type='json', methods=['POST'], auth='public', cors='*') @http.route('/api/manual_download_program', type='json', methods=['POST'], auth='wechat_token', cors='*')
@api_log('人工线下加工编程文件传输', requester='报工系统') @api_log('人工线下加工编程文件传输', requester='报工系统')
def manual_download_program(self): def manual_download_program(self):
""" """
@@ -59,7 +59,7 @@ class MainController(http.Controller):
target_ftp_info, target_ftp_info,
'/' + str(model_id), '/' + str(model_id),
'/', '/',
match_str=r'^\d*_\d*-' + tool_groups_id.name + r'-\w{2}-all\.nc$' match_str=r'^\d*-' + tool_groups_id.name + r'-\w{2}-all\.nc$'
) )
if len(result) > 0: if len(result) > 0:
return {'code': 200, 'message': '传输成功', 'file_list': result} return {'code': 200, 'message': '传输成功', 'file_list': result}

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

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

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

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

View File

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

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

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

View File

@@ -27,21 +27,27 @@ def api_log(name=None, requester=None):
# 执行原始函数 # 执行原始函数
result = func(*args, **kwargs) result = func(*args, **kwargs)
origin_result = result
if isinstance(result, str):
result = json.loads(result)
# 计算响应时间 # 计算响应时间
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', 500), 'status': 200 if status == 0 else status,
'requester': requester, 'requester': requester,
'responser': '智能工厂' 'responser': '智能工厂'
} }
@@ -49,7 +55,7 @@ def api_log(name=None, requester=None):
# 异步创建日志记录 # 异步创建日志记录
request.env['api.request.log'].sudo().with_context(tracking_disable=True).create(log_vals) request.env['api.request.log'].sudo().with_context(tracking_disable=True).create(log_vals)
return result return origin_result
except Exception as e: except Exception as e:
_logger.error(f"API日志记录失败: {str(e)}") _logger.error(f"API日志记录失败: {str(e)}")

View File

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

View File

@@ -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,85 +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):
service_categ = self.env.ref( # service_categ = self.env.ref(
'sf_dlm.product_category_surface_technics_sf').sudo() # '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

@@ -1360,9 +1360,9 @@ class Sf_Dashboard_Connect(http.Controller):
if result[0]: if result[0]:
if float(result[0]) >= 28800: if float(result[0]) >= 28800:
continue continue
alarm_last_24_time = float(result[0]) alarm_last_24_time += float(result[0])
else: else:
alarm_last_24_time = 0.0 alarm_last_24_time += 0.0
alarm_all_nums = [] alarm_all_nums = []
with conn.cursor() as cur: with conn.cursor() as cur:

View File

@@ -261,8 +261,50 @@ def transfer_files(
target_path = f"{target_dir}/{relative_path}/{item}" target_path = f"{target_dir}/{relative_path}/{item}"
else: else:
target_path = f"{target_dir}/{item}" target_path = f"{target_dir}/{item}"
with open(temp_path, 'rb') as f:
target_ftp.ftp.storbinary(f'STOR {target_path}', f) # 规范化路径
target_path = target_path.replace('\\', '/').strip('/')
# 确保目标目录存在
target_dir_path = '/'.join(target_path.split('/')[:-1])
try:
target_ftp.ftp.cwd('/') # 回到根目录
for dir_part in target_dir_path.split('/'):
if dir_part:
try:
target_ftp.ftp.cwd(dir_part)
except:
try:
target_ftp.ftp.mkd(dir_part)
target_ftp.ftp.cwd(dir_part)
except Exception as e:
logging.error(f"创建目录失败 {dir_part}: {str(e)}")
raise
except Exception as e:
logging.error(f"处理目标目录失败: {str(e)}")
raise
# 检查FTP连接状态
try:
target_ftp.ftp.voidcmd('NOOP')
except:
logging.error("FTP连接已断开尝试重新连接")
target_ftp.ftp.connect(target_ftp_info['host'], target_ftp_info['port'])
target_ftp.ftp.login(target_ftp_info['username'], target_ftp_info['password'])
# 上传文件
try:
with open(temp_path, 'rb') as f:
# 检查文件是否可读
content = f.read()
if not content:
raise Exception("临时文件为空")
f.seek(0) # 重置文件指针
target_ftp.ftp.storbinary(f'STOR {target_path}', f)
except Exception as e:
logging.error(f"上传文件失败: {str(e)}")
logging.error(f"目标路径: {target_path}")
raise
transfered_file_list.append(item) transfered_file_list.append(item)
# 删除临时文件 # 删除临时文件
@@ -270,37 +312,37 @@ def transfer_files(
logging.info(f"已传输文件: {item}") logging.info(f"已传输文件: {item}")
# 清空目标目录下的所有内容 # 清空目标目录下的所有内容
try: # try:
target_ftp.ftp.cwd(target_dir) # target_ftp.ftp.cwd(target_dir)
files = target_ftp.ftp.nlst() # files = target_ftp.ftp.nlst()
for f in files: # for f in files:
try: # try:
# 尝试删除文件 # # 尝试删除文件
target_ftp.ftp.delete(f) # target_ftp.ftp.delete(f)
except: # except:
try: # try:
# 如果删除失败,可能是目录,递归删除目录 # # 如果删除失败,可能是目录,递归删除目录
def remove_dir(path): # def remove_dir(path):
target_ftp.ftp.cwd(path) # target_ftp.ftp.cwd(path)
sub_files = target_ftp.ftp.nlst() # sub_files = target_ftp.ftp.nlst()
for sf in sub_files: # for sf in sub_files:
try: # try:
target_ftp.ftp.delete(sf) # target_ftp.ftp.delete(sf)
except: # except:
remove_dir(f"{path}/{sf}") # remove_dir(f"{path}/{sf}")
target_ftp.ftp.cwd('..') # target_ftp.ftp.cwd('..')
target_ftp.ftp.rmd(path) # target_ftp.ftp.rmd(path)
remove_dir(f"{target_dir}/{f}") # remove_dir(f"{target_dir}/{f}")
except: # except:
logging.error(f"无法删除 {f}") # logging.error(f"无法删除 {f}")
pass # pass
logging.info(f"已清空目标目录 {target_dir}") # logging.info(f"已清空目标目录 {target_dir}")
except Exception as e: # except Exception as e:
logging.error(f"清空目标目录失败: {str(e)}") # logging.error(f"清空目标目录失败: {str(e)}")
raise Exception(f"清空目标目录失败: {str(e)}") # raise Exception(f"清空目标目录失败: {str(e)}")
# 开始遍历 # 开始遍历
traverse_dir(source_dir) traverse_dir(source_dir)

View File

@@ -48,6 +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',
], ],
'assets': { 'assets': {

View File

@@ -6,12 +6,14 @@ from datetime import datetime
from odoo.addons.sf_manufacturing.models.agv_scheduling import RepeatTaskException from odoo.addons.sf_manufacturing.models.agv_scheduling import RepeatTaskException
from odoo import http from odoo import http
from odoo.http import request from odoo.http import request
from odoo.addons.sf_base.decorators.api_log import api_log
class Manufacturing_Connect(http.Controller): class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/GetWoInfo', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/GetWoInfo', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('获取工单', requester='中控系统')
def get_Work_Info(self, **kw): def get_Work_Info(self, **kw):
""" """
自动化传递工单号获取工单信息 自动化传递工单号获取工单信息
@@ -54,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):
""" """
自动化每天获取机台日计划 自动化每天获取机台日计划
@@ -107,6 +110,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/QcCheck', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/QcCheck', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('工件预调(前置三元检测)', requester='中控系统')
def get_qcCheck(self, **kw): def get_qcCheck(self, **kw):
""" """
工件预调(前置三元检测) 工件预调(前置三元检测)
@@ -149,6 +153,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/FeedBackStart', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/FeedBackStart', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('工单开始', requester='中控系统')
def button_Work_START(self, **kw): def button_Work_START(self, **kw):
""" """
工单任务开始 工单任务开始
@@ -198,6 +203,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/FeedBackEnd', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/FeedBackEnd', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('工单结束', requester='中控系统')
def button_Work_End(self, **kw): def button_Work_End(self, **kw):
""" """
工单任务结束 工单任务结束
@@ -249,6 +255,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/PartQualityInspect', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/PartQualityInspect', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('零件检测(后置三元检测)', requester='中控系统')
def PartQualityInspect(self, **kw): def PartQualityInspect(self, **kw):
""" """
零件质检 零件质检
@@ -295,6 +302,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/CMMProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/CMMProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('CMM测量程序下载', requester='中控系统')
def CMMProgDolod(self, **kw): def CMMProgDolod(self, **kw):
""" """
中控系统传递RFID编号给MES获取测量程序文件。Ftp下载文件 中控系统传递RFID编号给MES获取测量程序文件。Ftp下载文件
@@ -335,6 +343,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/NCProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/NCProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('CAM加工程序下载', requester='中控系统')
def NCProgDolod(self, **kw): def NCProgDolod(self, **kw):
""" """
中控系统传递RFID编号给MES获取程序单及程序文件。Ftp下载文件 中控系统传递RFID编号给MES获取程序单及程序文件。Ftp下载文件
@@ -376,6 +385,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/LocationChange', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/LocationChange', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('库位变更', requester='中控系统')
def LocationChange(self, **kw): def LocationChange(self, **kw):
""" """
库位变更 库位变更
@@ -435,32 +445,7 @@ class Manufacturing_Connect(http.Controller):
shelfinfo = list(filter(lambda x: x.get('DeviceId') == DeciveId, shelfinfo = list(filter(lambda x: x.get('DeviceId') == DeciveId,
request.env['sf.shelf.location'].sudo().get_sf_shelf_location_info( request.env['sf.shelf.location'].sudo().get_sf_shelf_location_info(
DeciveId))) DeciveId)))
total_data = request.env['sf.shelf.location.datasync'].sudo().get_total_data() request.env['sf.shelf.location.datasync'].sudo().set_shelf_location(shelfinfo)
for item in shelfinfo:
logging.info('货架已获取信息:%s' % item)
shelf_barcode = request.env['sf.shelf.location.datasync'].sudo().find_our_code(
total_data, item['Postion'])
location_id = request.env['sf.shelf.location'].sudo().search(
[('barcode', '=', shelf_barcode)],
limit=1)
if location_id:
# 如果是线边刀库信息,则对功能刀具移动生成记录
if 'Tool' in item['Postion']:
tool = request.env['sf.functional.cutting.tool.entity'].sudo().search(
[('rfid', '=', item['RfidCode']), ('functional_tool_status', '!=', '已拆除')])
tool.sudo().tool_in_out_stock_location(location_id)
if tool:
location_id.product_sn_id = tool.barcode_id.id
# 修改功能刀具状态
if item.get('State') == '报警':
if tool.functional_tool_status != item.get('State'):
tool.write({
'functional_tool_status': item['State']
})
else:
location_id.product_sn_id = False
if item['RfidCode']:
logging.info('Rfid为【%s】的功能刀具在系统中不存在!' % item['RfidCode'])
else: else:
equipment_id = request.env['maintenance.equipment'].sudo().search([('name', '=', DeciveId)]) equipment_id = request.env['maintenance.equipment'].sudo().search([('name', '=', DeciveId)])
if equipment_id: if equipment_id:
@@ -480,6 +465,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('AGV运送上产线', requester='中控系统')
def AGVToProduct(self, **kw): def AGVToProduct(self, **kw):
""" """
AGV运送上产线完成 AGV运送上产线完成
@@ -552,6 +538,7 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('AGV运送下产线', requester='中控系统')
def AGVDownProduct(self, **kw): def AGVDownProduct(self, **kw):
""" """
MES调度AGV搬运零件AGV托盘到产线接驳站。 MES调度AGV搬运零件AGV托盘到产线接驳站。

View File

@@ -27,7 +27,7 @@ class JikimoSaleRoutePicking(Sf_Bf_Connect):
bfm_process_order_list = json.loads(kw['bfm_process_order_list']) bfm_process_order_list = json.loads(kw['bfm_process_order_list'])
order_id = request.env['sale.order'].with_user(request.env.ref("base.user_admin")).sale_order_create( order_id = request.env['sale.order'].with_user(request.env.ref("base.user_admin")).sale_order_create(
company_id, kw['delivery_name'], kw['delivery_telephone'], kw['delivery_address'], company_id, kw['delivery_name'], kw['delivery_telephone'], kw['delivery_address'],
kw['delivery_end_date'], kw['payments_way'], kw['pay_way'], kw['order_number'], state='draft', kw['delivery_end_date'], kw['payments_way'], kw['pay_way'], kw['order_number'], kw['remark'], state='draft',
model_display_version=kw.get('model_display_version')) model_display_version=kw.get('model_display_version'))
i = 1 i = 1
# 给sale_order的default_code字段赋值 # 给sale_order的default_code字段赋值
@@ -45,6 +45,9 @@ 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') and kw.get('contract_code'):
order_id.create_sale_documents(kw.get('contract_file_name'), kw.get('contract_file'))
order_id.write({'contract_code': kw.get('contract_code'), 'contract_date': kw.get('contract_date')})
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,3 +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

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,41 +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]
cur_request_line['product_qty'] = len(request_line_list) # # cur_request_line['product_qty'] = cur_request_line['product_qty']
cur_request_line['request_id'] = pr.id # cur_request_line['request_id'] = pr.id
cur_request_line['origin'] = ", ".join({item['production_name'] for item in request_line_list if item.get('production_name')}) # cur_request_line['origin'] = ", ".join({item['production_name'] for item in request_line_list if item.get('production_name')})
cur_request_line.pop('group_id', None) # cur_request_line.pop('group_id', None)
cur_request_line.pop('production_name', None) # cur_request_line.pop('production_name', None)
self.env["purchase.request.line"].create(cur_request_line) # self.env["purchase.request.line"].create(cur_request_line)
pr.button_approved() # pr.button_approved()
# 外协出入库单处理 # 外协出入库单处理
def get_subcontract_pick_purchase(self): def get_subcontract_pick_purchase(self):
@@ -962,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:
@@ -1772,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:
@@ -1841,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,22 @@ 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')
# def _compute_pr_mp_count(self):
# for item in self:
# if not item.is_subcontract:
# item.pr_mp_count = 0
# continue
# pr_ids = self.env['purchase.request'].sudo().search(
# [('origin', 'like', item.production_id.name), ('is_subcontract', '=', 'True'),
# ('state', '!=', 'rejected')])
# if pr_ids:
# item.pr_mp_count = len(pr_ids)
# else:
# item.pr_mp_count = 0
@api.depends('state')
def _compute_pr_mp_count(self):
for item in self:
if not item.is_subcontract:
item.pr_mp_count = 0
continue
pr_ids = self.env['purchase.request'].sudo().search(
[('origin', 'like', item.production_id.name), ('is_subcontract', '=', 'True'),
('state', '!=', 'rejected')])
if pr_ids:
item.pr_mp_count = len(pr_ids)
else:
item.pr_mp_count = 0
@api.depends('state') @api.depends('state')
def _compute_back_button_display(self): def _compute_back_button_display(self):
for record in self: for record in self:
@@ -129,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:
@@ -220,22 +227,30 @@ class ResMrpWorkOrder(models.Model):
# finish_move.move_dest_ids.move_line_ids.reserved_uom_qty = 0 # finish_move.move_dest_ids.move_line_ids.reserved_uom_qty = 0
else: else:
next_workorder = sorted_workorders[position + 1] next_workorder = sorted_workorders[position + 1]
next_state = next_workorder.state # 持续获取下一个工单,直到找到一个不是返工的工单
if next_state not in ['pending', 'waiting', 'ready']: while next_workorder and next_workorder.state == 'rework':
raise UserError('下工序已经开始,无法回退') position += 1
if next_workorder.is_subcontract: if position + 1 < len(sorted_workorders):
next_workorder.picking_ids.write({'state': 'waiting'}) next_workorder = sorted_workorders[position + 1]
next_workorder.state = 'pending'
self.time_ids.date_end = None
cur_workorder.state = 'progress'
cur_workorder.production_id.state = 'progress'
quality_check = self.env['quality.check'].search(
[('workorder_id', '=', self.id)])
for check_order in quality_check:
if check_order.point_id.is_inspect:
check_order.quality_state = 'waiting'
else: else:
check_order.quality_state = 'none' next_workorder = None
if next_workorder:
next_state = next_workorder.state
if next_state not in ['pending', 'waiting', 'ready']:
raise UserError('下工序已经开始,无法回退')
if next_workorder.is_subcontract:
next_workorder.picking_ids.write({'state': 'waiting'})
next_workorder.state = 'pending'
self.time_ids.date_end = None
cur_workorder.state = 'progress'
cur_workorder.production_id.state = 'progress'
quality_check = self.env['quality.check'].search(
[('workorder_id', '=', self.id)])
for check_order in quality_check:
if check_order.point_id.is_inspect:
check_order.quality_state = 'waiting'
else:
check_order.quality_state = 'none'
def _compute_working_users(self): def _compute_working_users(self):
super()._compute_working_users() super()._compute_working_users()
@@ -439,15 +454,15 @@ 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:
if order.routing_type == '表面工艺' and order.state not in ['cancel']: if order.routing_type == '表面工艺' and order.state not in ['cancel']:
# domain = [('group_id', '=', self.production_id.procurement_group_id.id),
# ('purchase_type', '=', 'consignment'), ('state', '!=', 'cancel')]
domain = [('purchase_type', '=', 'consignment'), domain = [('purchase_type', '=', 'consignment'),
('origin', 'like', '%' + self.production_id.name + '%'), ('origin', 'like', '%' + self.production_id.name + '%'),
('state', '!=', 'cancel')] ('state', '!=', 'cancel')]
# domain = [('purchase_type', '=', 'consignment'),
# ('origin', 'like', '%' + self.production_id.name + '%'),
# ('state', '!=', 'cancel')]
purchase = self.env['purchase.order'].search(domain) purchase = self.env['purchase.order'].search(domain)
order.surface_technics_purchase_count = 0 order.surface_technics_purchase_count = 0
if not purchase: if not purchase:
@@ -460,30 +475,31 @@ class ResMrpWorkOrder(models.Model):
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()
# if self.routing_type == '表面工艺': # if self.routing_type == '表面工艺':
@@ -512,10 +528,11 @@ class ResMrpWorkOrder(models.Model):
return result return result
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'),
('state', '!=', 'cancel')]
# domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment')]
# domain = [('group_id', '=', self.production_id.procurement_group_id.id), ('purchase_type', '=', 'consignment')] # domain = [('group_id', '=', self.production_id.procurement_group_id.id), ('purchase_type', '=', 'consignment')]
purchase_orders = self.env['purchase.order'].search(domain, order='id desc', # 按创建时间降序(最新的在前) purchase_orders = self.env['purchase.order'].search(domain, order='id desc')
limit=1)
purchase_orders_id = self.env['purchase.order'] purchase_orders_id = self.env['purchase.order']
for po in purchase_orders: for po in purchase_orders:
for line in po.order_line: for line in po.order_line:
@@ -735,21 +752,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)]
@@ -1242,6 +1263,13 @@ class ResMrpWorkOrder(models.Model):
}] }]
return workorders_values_str return workorders_values_str
# def check_lot_exists(self, picking_id, lot_id):
# return bool(
# picking_id.move_ids.move_line_ids.filtered(
# lambda line: line.lot_id.id == lot_id
# )
# )
def _process_compute_state(self): def _process_compute_state(self):
sorted_workorders = sorted(self, key=lambda x: x.sequence) sorted_workorders = sorted(self, key=lambda x: x.sequence)
for workorder in sorted_workorders: for workorder in sorted_workorders:
@@ -1263,10 +1291,17 @@ class ResMrpWorkOrder(models.Model):
workorder.state = 'pending' workorder.state = 'pending'
continue continue
# ================= 如果制造订单制造类型为【人工线下加工】========================== # ================= 如果制造订单制造类型为【人工线下加工】==========================
# lot_id = workorder.production_id.move_raw_ids.move_line_ids.lot_id
# picking_ids = workorder.production_id.picking_ids.filtered(
# lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前')
# exists = any(
# move_line.lot_id == lot_id
# for picking in picking_ids
# for move in picking.move_ids
# for move_line in move.move_line_ids
# )
if (workorder.production_id.production_type == '人工线下加工' if (workorder.production_id.production_type == '人工线下加工'
and workorder.production_id.schedule_state == '已排' and workorder.production_id.schedule_state == '已排'):
and len(workorder.production_id.picking_ids.filtered(
lambda w: w.state not in ['done', 'cancel'])) == 0):
# and workorder.production_id.programming_state == '已编程' # and workorder.production_id.programming_state == '已编程'
if workorder.is_subcontract is True: if workorder.is_subcontract is True:
if workorder.production_id.state == 'rework': if workorder.production_id.state == 'rework':
@@ -1275,6 +1310,9 @@ class ResMrpWorkOrder(models.Model):
purchase_orders_id = self._get_surface_technics_purchase_ids() purchase_orders_id = self._get_surface_technics_purchase_ids()
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(
# lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
# move_out = picking_id.move_ids
move_out = workorder.move_subcontract_workorder_ids[1] move_out = workorder.move_subcontract_workorder_ids[1]
for mo in move_out: for mo in move_out:
if mo.state != 'done': if mo.state != 'done':
@@ -1315,6 +1353,10 @@ class ResMrpWorkOrder(models.Model):
if purchase_orders_id.state == 'purchase': if purchase_orders_id.state == 'purchase':
workorder.state = 'ready' workorder.state = 'ready'
move_out = workorder.move_subcontract_workorder_ids[1] move_out = workorder.move_subcontract_workorder_ids[1]
# picking_id = workorder.production_id.picking_ids.filtered(
# lambda
# wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
# move_out = picking_id.move_ids
for mo in move_out: for mo in move_out:
if mo.state != 'done': if mo.state != 'done':
mo.write({'state': 'assigned', 'production_id': False}) mo.write({'state': 'assigned', 'production_id': False})
@@ -1324,7 +1366,6 @@ class ResMrpWorkOrder(models.Model):
else: else:
workorder.state = 'waiting' workorder.state = 'waiting'
@api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state', @api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state',
'production_id.tool_state', 'production_id.schedule_state', 'sequence', 'production_id.tool_state', 'production_id.schedule_state', 'sequence',
'production_id.programming_state') 'production_id.programming_state')
@@ -1356,11 +1397,11 @@ class ResMrpWorkOrder(models.Model):
# 判断是否有坯料的序列号信息 # 判断是否有坯料的序列号信息
boolean = False boolean = False
if self.production_id.move_raw_ids: if self.production_id.move_raw_ids:
if self.production_id.move_raw_ids[0].product_id.categ_type == '坯料': if self.production_id.move_raw_ids[0].product_id.categ_type == '坯料' and \
self.production_id.move_raw_ids[0].product_id.tracking == 'serial':
if self.production_id.move_raw_ids[0].move_line_ids: if self.production_id.move_raw_ids[0].move_line_ids:
if self.production_id.move_raw_ids[0].move_line_ids: if self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name:
if self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name: boolean = True
boolean = True
else: else:
boolean = True boolean = True
if not boolean: if not boolean:
@@ -1390,6 +1431,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:
move_out = self.move_subcontract_workorder_ids[1] move_out = self.move_subcontract_workorder_ids[1]
# picking_id = self.production_id.picking_ids.filtered(
# lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
# move_out = picking_id.move_ids
# move_out = self.move_subcontract_workorder_ids[1]
# move_out = self.env['stock.move'].search( # move_out = self.env['stock.move'].search(
# [('location_id', '=', self.env['stock.location'].search( # [('location_id', '=', self.env['stock.location'].search(
# [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id), # [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id),
@@ -1555,25 +1600,17 @@ class ResMrpWorkOrder(models.Model):
len(done_workorder) == len( len(done_workorder) == len(
record.production_id.workorder_ids.filtered(lambda wo: wo.state != 'cancel'))): record.production_id.workorder_ids.filtered(lambda wo: wo.state != 'cancel'))):
is_production_id = True is_production_id = True
if record.routing_type in ['解除装夹'] or (
record.is_rework is True and record.routing_type in ['装夹预调']):
for workorder in record.production_id.workorder_ids:
if workorder.processing_panel == record.processing_panel:
rfid_code = workorder.rfid_code
if record.is_rework is not True:
workorder.write({'rfid_code_old': rfid_code, 'rfid_code': False})
elif workorder.routing_type != '装夹预调' and workorder.state != 'rework':
workorder.write({'rfid_code_old': False, 'rfid_code': False})
elif workorder.routing_type == '装夹预调' and workorder.state != 'rework':
workorder.write({'rfid_code_old': rfid_code, 'rfid_code': False})
self.env['stock.lot'].sudo().search([('rfid', '=', rfid_code)]).write(
{'tool_material_status': '可用'})
if workorder.rfid_code:
raise ValidationError(f'{workorder.name}】工单解绑失败,请重新点击完成按钮!!!')
# workorder.rfid_code_old = rfid_code
# workorder.rfid_code = False
logging.info('workorder.rfid_code:%s' % workorder.rfid_code)
if record.routing_type in ['解除装夹']:
rfid_code = record.rfid_code
work_ids = record.production_id.workorder_ids.filtered(
lambda wo: wo.processing_panel == record.processing_panel and wo.state != 'rework')
work_ids.write({'rfid_code_old': rfid_code, 'rfid_code': False})
self.env['stock.lot'].sudo().search([('rfid', '=', rfid_code)]).write(
{'tool_material_status': '可用'})
if any(wo.rfid_code for wo in work_ids):
raise ValidationError(f'{record.name}】工单解绑失败,请重新点击完成按钮!!!')
logging.info('work_ids.rfid_code:%s' % [wo.rfid_code for wo in work_ids])
if is_production_id is True: if is_production_id is True:
logging.info('product_qty:%s' % record.production_id.product_qty) logging.info('product_qty:%s' % record.production_id.product_qty)
for move_raw_id in record.production_id.move_raw_ids: for move_raw_id in record.production_id.move_raw_ids:
@@ -1808,7 +1845,7 @@ class ResMrpWorkOrder(models.Model):
orderby=orderby, orderby=orderby,
lazy=lazy lazy=lazy
) )
model_id = fields.Char('模型ID', related='production_id.model_id') model_id = fields.Char('模型ID', related='production_id.model_id')

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)
@@ -1030,6 +1030,7 @@ class ResProductMo(models.Model):
'single_manufacturing': product_id.single_manufacturing, 'single_manufacturing': product_id.single_manufacturing,
'is_bfm': True, 'is_bfm': True,
'active': True, 'active': True,
'tracking': finish_product.tracking, # 坯料的跟踪方式跟随成品
} }
# 外协和采购生成的坯料需要根据材料型号绑定供应商 # 外协和采购生成的坯料需要根据材料型号绑定供应商
if route_type == 'subcontract' or route_type == 'purchase': if route_type == 'subcontract' or route_type == 'purchase':

View File

@@ -59,83 +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', '=', product_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:
continue # 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: # 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_id # move_out = picking_id.move_ids
for mo in move_out: # for mo in move_out:
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:
self.env['stock.move.line'].create( # self.env['stock.move.line'].create(
mo.get_move_line(production_id, min_sequence_wk)) # 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
@@ -146,8 +146,15 @@ 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()
return super(PurchaseOrder, self).button_confirm()
res = super(PurchaseOrder, self).button_confirm()
for line in self.order_line:
# 将产品不追踪序列号的行项目设置qty_done
if line.move_ids and line.move_ids[0].product_id.tracking == 'none':
line.move_ids[0].quantity_done = line.move_ids[0].product_qty
return res
origin_sale_id = fields.Many2one('sale.order', string='销售订单号', store=True, compute='_compute_origin_sale_id') origin_sale_id = fields.Many2one('sale.order', string='销售订单号', store=True, compute='_compute_origin_sale_id')
@@ -201,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)

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,7 +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
order_id = self order_id = self
product = line.product_id product = line.product_id
@@ -75,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)
@@ -189,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

@@ -2,79 +2,79 @@
import logging import logging
from odoo import fields, models, api from odoo import fields, models, api
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError, ValidationError
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'
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="工序")
@api.depends('outsourced_service_products') # @api.depends('outsourced_service_products')
def _compute_service_products(self): # def _compute_service_products(self):
for record in self: # for record in self:
# 假设取第一条作为主明细 # # 假设取第一条作为主明细
record.service_products = record.outsourced_service_products.id if record.outsourced_service_products else False # record.service_products = record.outsourced_service_products.ids if record.outsourced_service_products else False
def _inverse_service_products(self): # def _inverse_service_products(self):
for record in self: # for record in self:
if record.service_products: # if record.service_products:
# 确保关联关系正确 # # 确保关联关系正确
record.outsourced_service_products = record.service_products.ids if record.service_products else False # record.outsourced_service_products = record.service_products.ids if record.service_products else False
else: # else:
record.outsourced_service_products = False # record.outsourced_service_products = False
def name_get(self): # def name_get(self):
result = [] # result = []
for record in self: # for record in self:
name = f"{record.process_id.name} - {record.name}" # 自定义显示格式 # name = f"{record.process_id.name} - {record.name}" # 自定义显示格式
result.append((record.id, name)) # result.append((record.id, name))
return result # return result
@api.constrains('outsourced_service_products') # @api.constrains('outsourced_service_products')
def _validate_partner_limit(self): # def _validate_partner_limit(self):
for record in self: # for record in self:
if len(record.outsourced_service_products) > 1: # if len(record.outsourced_service_products) > 1:
raise ValidationError("工艺参数不能与多个产品关联") # raise ValidationError("工艺参数不能与多个产品关联")
@api.onchange('outsourced_service_products') # @api.onchange('outsourced_service_products')
def _onchange_validate_partner_limit(self): # def _onchange_validate_partner_limit(self):
for record in self: # for record in self:
if len(record.outsourced_service_products) > 1: # if len(record.outsourced_service_products) > 1:
raise ValidationError("工艺参数不能与多个产品关联") # raise ValidationError("工艺参数不能与多个产品关联")
@api.depends('outsourced_service_products') # @api.depends('outsourced_service_products')
def _compute_is_product_button(self): # def _compute_is_product_button(self):
for record in self: # for record in self:
if record.outsourced_service_products: # if record.outsourced_service_products:
record.is_product_button = True # record.is_product_button = True
else: # else:
record.is_product_button = False # record.is_product_button = False
def has_wksp_prefix(self): # def has_wksp_prefix(self):
""" # """
判断字符串是否以WKSP开头不区分大小写 # 判断字符串是否以WKSP开头不区分大小写
:param text: 要检查的字符串 # :param text: 要检查的字符串
:return: True/False # :return: True/False
""" # """
return self.code.upper().startswith('101'+self.routing_id.code) # return self.code.upper().startswith('101'+self.routing_id.code)
@api.depends('outsourced_service_products','code') # @api.depends('outsourced_service_products','code')
def _compute_is_delete_button(self): # def _compute_is_delete_button(self):
for record in self: # for record in self:
if record.outsourced_service_products and record.has_wksp_prefix(): # if record.outsourced_service_products and record.has_wksp_prefix():
record.is_delete_button = False # record.is_delete_button = False
elif record.outsourced_service_products: # elif record.outsourced_service_products:
record.is_delete_button = True # record.is_delete_button = True
else: # else:
record.is_delete_button = True # 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'):
@@ -90,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': '创建服务产品',
@@ -116,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,6 +638,62 @@ 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
# product_ids = self.move_ids.mapped('product_id')
# if not self.move_ids[0].product_id.single_manufacturing and self.move_ids[0].product_id.tracking == 'none':
# lot_ids = self.move_ids.move_line_ids.mapped('lot_id')
# production_ids = self.sale_order_id.mrp_production_ids if self.sale_order_id else self.env['mrp.production']
# if res and self.location_id.name == '外协收料区' and self.location_dest_id.name == '制造前':
# # 如果是最后一张外协入库单,则设置库存位置的预留数量
# for production_id in production_ids:
# if lot_ids:
# lot_id = production_id.move_raw_ids.move_line_ids.lot_id
# # picking_ids = production_id.picking_ids.filtered(
# # lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前')
# if lot_id in lot_ids:
# workorder_id = production_id.workorder_ids.filtered(
# lambda a: a.state == 'progress' and a.is_subcontract)
# if not workorder_id:
# continue
# workorder_id.button_finish()
# else:
# workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'progress' and a.is_subcontract)
# if not workorder_id:
# continue
# workorder_id.button_finish()
# # lot_id = workorder.production_id.move_raw_ids.move_line_ids.lot_id
# # picking_ids = workorder.production_id.picking_ids.filtered(
# # lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前')
# # if move_in:
# # workorder = move_in.subcontract_workorder_id
# # workorders = workorder.production_id.workorder_ids
# # subcontract_workorders = workorders.filtered(
# # lambda wo: wo.is_subcontract == True and wo.state != 'cancel').sorted('sequence')
# # # if workorder == subcontract_workorders[-1]:
# # # self.env['stock.quant']._update_reserved_quantity(
# # # move_in.product_id, move_in.location_dest_id, move_in.product_uom_qty,
# # # lot_id=move_in.move_line_ids.lot_id,
# # # package_id=False, owner_id=False, strict=False
# # # )
# # workorder.button_finish()
# if res and self.location_id.name == '制造前' and self.location_dest_id.name == '外协加工区':
# for production_id in production_ids:
# if lot_ids:
# lot_id = production_id.move_raw_ids.move_line_ids.lot_id
# # picking_ids = production_id.picking_ids.filtered(
# # lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前')
# if lot_id in lot_ids:
# workorder_id = production_id.workorder_ids.filtered(
# lambda a: a.state == 'progress' and a.is_subcontract)
# if not workorder_id:
# continue
# workorder_id.button_finish()
# else:
# workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'ready' and a.is_subcontract)
# if not workorder_id:
# continue
# workorder_id.button_start()
picking_type_in = self.env.ref('sf_manufacturing.outcontract_picking_in').id 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: if res is True and self.picking_type_id.id == picking_type_in:
# 如果是最后一张外协入库单,则设置库存位置的预留数量 # 如果是最后一张外协入库单,则设置库存位置的预留数量
@@ -675,6 +738,7 @@ class StockPicking(models.Model):
# 创建 外协出库入单 # 创建 外协出库入单
def create_outcontract_picking(self, workorders, item, sorted_workorders): def create_outcontract_picking(self, workorders, item, sorted_workorders):
production = workorders[0].production_id
for workorder in workorders: for workorder in workorders:
if workorder.move_subcontract_workorder_ids: if workorder.move_subcontract_workorder_ids:
workorder.move_subcontract_workorder_ids.write({'state': 'cancel'}) workorder.move_subcontract_workorder_ids.write({'state': 'cancel'})
@@ -706,7 +770,7 @@ class StockPicking(models.Model):
}) })
moves_in = self.env['stock.move'].sudo().with_context(context).create( moves_in = self.env['stock.move'].sudo().with_context(context).create(
self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_in, self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_in,
procurement_group_id.id, move_dest_id)) procurement_group_id.id, move_dest_id, production.product_uom_qty))
picking_in = self.create( picking_in = self.create(
moves_in._get_new_picking_values_Res(item, workorder, 'WH/OCIN/')) moves_in._get_new_picking_values_Res(item, workorder, 'WH/OCIN/'))
# pick_ids.append(picking_in.id) # pick_ids.append(picking_in.id)
@@ -716,7 +780,7 @@ class StockPicking(models.Model):
# self.env.context.get('default_production_id') # self.env.context.get('default_production_id')
moves_out = self.env['stock.move'].sudo().with_context(context).create( moves_out = self.env['stock.move'].sudo().with_context(context).create(
self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_out, self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_out,
procurement_group_id.id, moves_in.id)) procurement_group_id.id, moves_in.id, production.product_uom_qty))
workorder.write({'move_subcontract_workorder_ids': [(6, 0, [moves_in.id, moves_out.id])]}) workorder.write({'move_subcontract_workorder_ids': [(6, 0, [moves_in.id, moves_out.id])]})
picking_out = self.create( picking_out = self.create(
moves_out._get_new_picking_values_Res(item, workorder, 'WH/OCOUT/')) moves_out._get_new_picking_values_Res(item, workorder, 'WH/OCOUT/'))
@@ -782,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):
@@ -792,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)
@@ -824,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)
@@ -848,7 +913,7 @@ class ReStockMove(models.Model):
traceback_error = traceback.format_exc() traceback_error = traceback.format_exc()
logging.error("零件图号 零件名称获取失败:%s" % traceback_error) logging.error("零件图号 零件名称获取失败:%s" % traceback_error)
def _get_stock_move_values_Res(self, item, picking_type_id, group_id, move_dest_ids=False): def _get_stock_move_values_Res(self, item, picking_type_id, group_id, move_dest_ids=False, product_uom_qty=1.0):
route_id = self.env.ref('sf_manufacturing.route_surface_technology_outsourcing').id route_id = self.env.ref('sf_manufacturing.route_surface_technology_outsourcing').id
stock_rule = self.env['stock.rule'].sudo().search( stock_rule = self.env['stock.rule'].sudo().search(
[('route_id', '=', route_id), ('picking_type_id', '=', picking_type_id)]) [('route_id', '=', route_id), ('picking_type_id', '=', picking_type_id)])
@@ -857,7 +922,7 @@ class ReStockMove(models.Model):
'company_id': item.company_id.id, 'company_id': item.company_id.id,
'product_id': item.bom_id.bom_line_ids.product_id.id, 'product_id': item.bom_id.bom_line_ids.product_id.id,
'product_uom': item.bom_id.bom_line_ids.product_uom_id.id, 'product_uom': item.bom_id.bom_line_ids.product_uom_id.id,
'product_uom_qty': 1.0, 'product_uom_qty': product_uom_qty,
'location_id': stock_rule.location_src_id.id, 'location_id': stock_rule.location_src_id.id,
'location_dest_id': stock_rule.location_dest_id.id, 'location_dest_id': stock_rule.location_dest_id.id,
'origin': item.name, 'origin': item.name,
@@ -902,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': 1.0, '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

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

View File

@@ -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

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

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

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

View File

@@ -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

@@ -46,10 +46,10 @@ class ProductionWizard(models.TransientModel):
mrp_workorder_list = self.mrp_production_id.workorder_ids.filtered(lambda kw: kw.rfid_code) mrp_workorder_list = self.mrp_production_id.workorder_ids.filtered(lambda kw: kw.rfid_code)
for workorder in mrp_workorder_list: for workorder in mrp_workorder_list:
rfid_code = workorder.rfid_code rfid_code = workorder.rfid_code
workorder.filtered(lambda wo: wo.routing_type == '装夹预调' and wo.rfid_code is not False).write( workorder.filtered(lambda wo: wo.routing_type == '装夹预调' and wo.rfid_code and wo.state != 'rework').write(
{'rfid_code_old': rfid_code, 'rfid_code': False}) {'rfid_code_old': rfid_code, 'rfid_code': False})
workorder.filtered(lambda wo: (wo.routing_type != '装夹预调' and workorder.filtered(lambda wo: (wo.routing_type != '装夹预调' and
(wo.rfid_code_old is not False or wo.rfid_code is not False))).write( (wo.rfid_code_old or wo.rfid_code) and wo.state != 'rework')).write(
{'rfid_code_old': False, 'rfid_code': False}) {'rfid_code_old': False, 'rfid_code': False})
if self.is_remanufacture is True: if self.is_remanufacture is True:

View File

@@ -125,13 +125,28 @@ 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 == '装夹预调')
if clamp_workorder_ids:
for clamp_workorder_id in clamp_workorder_ids: # for order in rework_workorder_ids:
self.production_id.workorder_ids.filtered( # order.write({
lambda wk: wk.processing_panel == clamp_workorder_id.processing_panel).write( # 'rfid_code_old': order.rfid_code,
{'rfid_code': None}) # 'rfid_code': False
# })
# 返工工单状态设置为【返工】 # 返工工单状态设置为【返工】
rework_workorder_ids.write({'state': 'rework'}) rework_workorder_ids.write({'state': 'rework'})
if clamp_workorder_ids:
for clamp_workorder_id in clamp_workorder_ids:
# 清除返工的装夹预调工单以及其他同面返工工单的RFID并保存rfid记录
self.production_id.workorder_ids.filtered(lambda wk: (
wk.processing_panel == clamp_workorder_id.processing_panel
and wk.state == 'rework' and wk.rfid_code)).write(
{'rfid_code_old': clamp_workorder_id.rfid_code, 'rfid_code': None})
# 清除返工的装夹预调工单同面的非返工工单的RFID
self.production_id.workorder_ids.filtered(lambda wk: (
wk.processing_panel == clamp_workorder_id.processing_panel and wk.state != 'rework')).write(
{'rfid_code_old': None, 'rfid_code': None})
# 清除其他返工工单的RFID
for work in rework_workorder_ids.filtered(lambda wk: wk.rfid_code):
work.write({'rfid_code_old': work.rfid_code, 'rfid_code': None})
# 查询返工工单对应的工艺设计记录,并调用方法拼接数据,用于创建新的工单 # 查询返工工单对应的工艺设计记录,并调用方法拼接数据,用于创建新的工单
workorders_values = [] workorders_values = []
for work in rework_workorder_ids: for work in rework_workorder_ids:
@@ -158,8 +173,12 @@ class ReworkWizard(models.TransientModel):
# ====新工单绑定rfid=== # ====新工单绑定rfid===
for new_work_id in new_work_ids: for new_work_id in new_work_ids:
if new_work_id.routing_type in ['CNC加工', '解除装夹']: if new_work_id.routing_type in ['CNC加工', '解除装夹']:
new_work_id.write({'rfid_code': self.production_id.workorder_ids.filtered( # 获取new_work_id同一个加工面已经绑定rfid的非返工的装夹预调工单
lambda wk: wk.sequence == new_work_id.sequence - 1).rfid_code}) work_id = self.production_id.workorder_ids.filtered(
lambda wk: (wk.processing_panel == new_work_id.processing_panel and wk.rfid_code
and wk.routing_type == '装夹预调' and wk.state != 'rework'))
if work_id:
new_work_id.write({'rfid_code': work_id.rfid_code})
self.production_id.detection_result_ids.filtered( self.production_id.detection_result_ids.filtered(
lambda ap1: ap1.handle_result == '待处理').write({'handle_result': '已处理'}) lambda ap1: ap1.handle_result == '待处理').write({'handle_result': '已处理'})
panels = [] # 返工的加工面 panels = [] # 返工的加工面
@@ -213,11 +232,13 @@ class ReworkWizard(models.TransientModel):
self.production_id.get_new_program(panel_name) self.production_id.get_new_program(panel_name)
if self.reprogramming_num >= 0 and self.programming_state == '已下发': if self.reprogramming_num >= 0 and self.programming_state == '已下发':
# ============= 处理CNC加工加工工单的 CNC程序和cmm程序 信息============= # ============= 处理CNC加工加工工单的 CNC程序和cmm程序 信息=============
for cnc_work in new_work_ids.filtered(lambda wk: wk.name == 'CNC加工'): for cnc_work in new_work_ids.filtered(
lambda wk: wk.name == 'CNC加工' or wk.name == '人工线下加工'):
ret = {'programming_list': []} ret = {'programming_list': []}
old_cnc_rework = max(self.production_id.workorder_ids.filtered( old_cnc_rework = max(self.production_id.workorder_ids.filtered(
lambda crw: crw.processing_panel == cnc_work.processing_panel lambda crw: crw.processing_panel == cnc_work.processing_panel
and crw.state == 'rework' and crw.routing_type == 'CNC加工'), and crw.state == 'rework' and (
crw.routing_type == 'CNC加工' or crw.routing_type == '人工线下加工')),
key=lambda w: w.create_date key=lambda w: w.create_date
) )
# 获取当前工单的CNC程序和cmm程序 # 获取当前工单的CNC程序和cmm程序
@@ -259,7 +280,8 @@ class ReworkWizard(models.TransientModel):
new_cnc_workorder = self.production_id.workorder_ids.filtered( new_cnc_workorder = self.production_id.workorder_ids.filtered(
lambda ap1: ap1.processing_panel == cnc_work.processing_panel lambda ap1: ap1.processing_panel == cnc_work.processing_panel
and ap1.state not in ( and ap1.state not in (
'rework', 'done') and ap1.routing_type == 'CNC加工' 'rework', 'done') and (
ap1.routing_type == 'CNC加工' or ap1.routing_type == '人工线下加工')
) )
if not new_cnc_workorder.cnc_ids: if not new_cnc_workorder.cnc_ids:
new_cnc_workorder.write({ new_cnc_workorder.write({
@@ -268,6 +290,8 @@ class ReworkWizard(models.TransientModel):
'cmm_ids': new_cnc_workorder.cmm_ids.sudo()._json_cmm_program( 'cmm_ids': new_cnc_workorder.cmm_ids.sudo()._json_cmm_program(
cnc_work.processing_panel, ret), cnc_work.processing_panel, ret),
'cnc_worksheet': old_cnc_rework.cnc_worksheet}) 'cnc_worksheet': old_cnc_rework.cnc_worksheet})
# 复制装夹图纸
new_cnc_workorder.processing_drawing = old_cnc_rework.processing_drawing
# ========== 处理装夹预调 【装夹图纸】 数据 ================ # ========== 处理装夹预调 【装夹图纸】 数据 ================
for new_pre_work in new_pre_workorder_ids: for new_pre_work in new_pre_workorder_ids:
pre_rework = max(self.production_id.workorder_ids.filtered( pre_rework = max(self.production_id.workorder_ids.filtered(
@@ -294,7 +318,8 @@ class ReworkWizard(models.TransientModel):
'is_rework': False}) 'is_rework': False})
# ==================申请重新编程======================= # ==================申请重新编程=======================
if self.is_reprogramming is True: if self.is_reprogramming is True:
self.production_id.update_programming_state(trigger_time=datetime.now().strftime('%Y-%m-%d %H:%M:%S')) self.production_id.update_programming_state(
trigger_time=datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
self.production_id.write( self.production_id.write(
{'programming_state': '编程中', 'work_state': '编程中', 'state': 'progress'}) {'programming_state': '编程中', 'work_state': '编程中', 'state': 'progress'})
# ================= 返工完成,制造订单状态置为加工中 ============== # ================= 返工完成,制造订单状态置为加工中 ==============
@@ -303,18 +328,22 @@ class ReworkWizard(models.TransientModel):
@api.onchange('production_id') @api.onchange('production_id')
def onchange_processing_panel_id(self): def onchange_processing_panel_id(self):
for item in self: for item in self:
panel_ids = []
domain = [('id', '=', False)] domain = [('id', '=', False)]
production_id = item.production_id production_id = item.production_id
if production_id: if production_id:
if self.env.user.has_group('sf_base.group_sf_order_user'): if self.env.user.has_group('sf_base.group_sf_order_user'):
panel_ids = []
panel_arr = production_id.product_id.model_processing_panel panel_arr = production_id.product_id.model_processing_panel
if panel_arr is False: if panel_arr is False:
break break
for p in production_id.detection_result_ids.filtered( for p in production_id.detection_result_ids.filtered(
lambda ap1: ap1.handle_result == '待处理'): lambda ap1: ap1.handle_result == '待处理'):
if p.processing_panel is not False and p.processing_panel not in panel_arr: if p.processing_panel is not False and p.processing_panel not in panel_arr:
panel_arr += ','.join(p.processing_panel) if len(panel_arr) > 0:
panel_arr += ','.join(p.processing_panel)
else:
panel_arr = p.processing_panel
for item in panel_arr.split(','): for item in panel_arr.split(','):
panel = self.env['sf.processing.panel'].search( panel = self.env['sf.processing.panel'].search(
[('name', 'ilike', item)]) [('name', 'ilike', item)])

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

@@ -210,8 +210,8 @@
<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">### 功能刀具寿命到期提醒:
单号:拆解单[{{code}}]({{tool_expired_remind_special_url}}) 单号:拆解单[{{code}}]({{request_url}})
事项:{{functional_tool_id.tool_name_id.name}}寿命已到期,需拆解</field> 事项:{{name}}寿命已到期,需拆解</field>
</record> </record>
<record id="template_tool_assembly_remind" model="jikimo.message.template"> <record id="template_tool_assembly_remind" model="jikimo.message.template">
@@ -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

@@ -1,4 +1,5 @@
from odoo import models, api from odoo import models, api
from urllib.parse import urlencode
class SFMessagefunctionalToolDismantle(models.Model): class SFMessagefunctionalToolDismantle(models.Model):
@@ -18,12 +19,39 @@ class SFMessagefunctionalToolDismantle(models.Model):
obj.add_queue('功能刀具寿命到期') obj.add_queue('功能刀具寿命到期')
return result return result
def get_special_url(self,id,tmplate_name,special_name,model_id): 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
td_line = self.search([('id', '=', int(message_queue_id.res_id))])
url = self.request_url(int(message_queue_id.res_id))
content = content.replace('{{code}}', td_line.code).replace(
'{{request_url}}', url).replace('{{name}}', td_line.name)
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('sf_tool_management.sf_functional_tool_dismantle_view_act').id
menu_id = self.env.ref('sf_tool_management.menu_sf_functional_tool_dismantle').id
# 查询参数
params = {'id': id, 'menu_id': menu_id, 'action': action_id,
'model': 'sf.functional.tool.dismantle',
'view_type': 'form'}
# 拼接查询参数
tool_string = urlencode(params)
# 拼接URL
full_url = url + "/web#" + tool_string
return full_url
def get_special_url(self, id, tmplate_name, special_name, model_id):
menu_id = 0 menu_id = 0
action_id = 0 action_id = 0
if tmplate_name=='调拨入库' and special_name== 'tool_expired_remind_special_url': if tmplate_name == '调拨入库' and special_name == 'tool_expired_remind_special_url':
menu_id = self.env.ref('mrp.menu_mrp_root').id menu_id = self.env.ref('mrp.menu_mrp_root').id
action_id = self.env.ref('sf_tool_management.sf_functional_tool_dismantle_view_act').id action_id = self.env.ref('sf_tool_management.sf_functional_tool_dismantle_view_act').id
return super(SFMessagefunctionalToolDismantle, self).get_url(id, menu_id, action_id,model_id) return super(SFMessagefunctionalToolDismantle, self).get_url(id, menu_id, action_id, model_id)
else: else:
return super(SFMessagefunctionalToolDismantle, self).get_special_url(id, tmplate_name, special_name, model_id) return super(SFMessagefunctionalToolDismantle, self).get_special_url(id, tmplate_name, special_name,
model_id)

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,46 @@
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 = {}
for item in self:
original_state.update({f'{item.id}': item.state})
res = super(SFMessagePurchaseRequest, self).write(vals)
for item in self:
if vals.get('state') == 'approved' and original_state.get(f'{item.id}') != 'approved':
item.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,73 @@ 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 = {}
for item in self:
original_state.update({f'{item.id}': item.quality_state})
res = super(SFMessageQualityCheck, self).write(vals)
for item in self:
if vals.get('quality_state') == 'none' and original_state.get(f'{item.id}') != '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', '=', item.id)])
if not queue_id and '制造' not in [pt.name for pt in item.point_id.picking_type_ids]:
item.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

@@ -24,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)
@@ -59,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加工的工单
@@ -76,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(
@@ -95,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, "扫码获取工单") 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'
@@ -134,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)
@@ -240,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)

View File

@@ -1135,8 +1135,6 @@ class sfProductionProcessParameter(models.Model):
[("code", '=', item['code']), ('active', 'in', [True, False])]) [("code", '=', item['code']), ('active', 'in', [True, False])])
process = self.env['sf.production.process'].search( process = self.env['sf.production.process'].search(
[('code', '=', item['process_id_code'])], limit=1) [('code', '=', item['process_id_code'])], limit=1)
production_process_parameter = self.search(
[("code", '=', item['code']), ('active', 'in', [True, False])])
if not production_process_parameter: if not production_process_parameter:
production_process_parameter = self.create({ production_process_parameter = self.create({
"name": item['name'], "name": item['name'],
@@ -1151,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']
@@ -1163,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', '已排程'),
@@ -228,7 +229,7 @@ class sf_production_plan(models.Model):
""" """
排程方法 排程方法
""" """
self.deal_processing_schedule(self.date_planned_start) self.deal_processing_schedule(self[0].date_planned_start)
for record in self: for record in self:
if not record.production_line_id: if not record.production_line_id:
raise ValidationError("未选择生产线") raise ValidationError("未选择生产线")

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,6 +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, float_compare
class QualityCheck(models.Model): class QualityCheck(models.Model):
@@ -125,10 +126,178 @@ class QualityCheck(models.Model):
# todo 需修改 # todo 需修改
val = ['0037818516'] val = ['0037818516']
logging.info('获取到的工单信息%s' % val) logging.info('获取到的工单信息%s' % val)
r = requests.post(crea_url, json=val, headers=headers) # r = requests.post(crea_url, json=val, headers=headers)
r = self.env['api.request.log'].log_request(
'get',
crea_url,
name='零件特采',
responser='中控系统',
json=val,
headers=headers
)
ret = r.json() ret = r.json()
logging.info('_register_quality_check:%s' % ret) logging.info('_register_quality_check:%s' % ret)
if ret['Succeed']: if ret['Succeed']:
return "零件特采发送成功" return "零件特采发送成功"
else: else:
raise ValidationError("零件特采发送失败") raise ValidationError("零件特采发送失败")
@api.model_create_multi
def create(self, vals_list):
for val in vals_list:
if 'point_id' in val and 'measure_on' not in val:
# 如果没有控制方式字段,则从检查点读取质量方式
point_id = self.env['quality.point'].browse(val['point_id'])
val.update({'measure_on': point_id.measure_on})
return super(QualityCheck, self).create(vals_list)
@api.depends('total_qty','testing_percentage_within_lot', 'is_lot_tested_fractionally')
def _compute_workorder_qty_to_test(self):
for qc in self:
if qc.is_lot_tested_fractionally:
rounding = qc.product_id.uom_id.rounding if qc.product_id.uom_id else 0.01
qc.workorder_qty_to_test = float_round(float(qc.total_qty) * qc.testing_percentage_within_lot / 100,
precision_rounding=rounding, rounding_method="UP")
else:
qc.workorder_qty_to_test = qc.total_qty
@api.depends('picking_id', 'workorder_id')
def _compute_total_qty(self):
super(QualityCheck, self)._compute_total_qty()
for qc in self:
if not qc.picking_id and qc.workorder_id:
qc.total_qty = qc.workorder_id.production_id.product_qty
@api.depends('workorder_qty_to_test')
def _compute_workorder_qty_tested(self):
for qc in self:
qc.workorder_qty_tested = qc.workorder_qty_to_test
workorder_qty_to_test = fields.Float('应检', compute='_compute_workorder_qty_to_test', store=True)
workorder_qty_tested = fields.Float('已检', compute='_compute_workorder_qty_tested', store=True)
workorder_qty_test_failed = fields.Float('不合格数')
@api.onchange('total_qty', 'workorder_qty_test_failed', 'workorder_qty_to_test', 'workorder_qty_tested')
def _onchage_qty(self):
for record in self:
if record.total_qty and record.workorder_qty_to_test and record.workorder_qty_to_test > float(record.total_qty):
record.workorder_qty_to_test = float(record.total_qty)
return {
'warning': {
'title': '警告',
'message': '待检数量不能超过总数量'
}
}
if record.workorder_qty_to_test and record.workorder_qty_tested and record.workorder_qty_tested > record.workorder_qty_to_test:
record.workorder_qty_tested = record.workorder_qty_to_test
return {
'warning': {
'title': '警告',
'message': '已检数量不能超过待检数量'
}
}
if record.workorder_qty_tested and record.workorder_qty_test_failed and record.workorder_qty_test_failed > record.workorder_qty_tested:
record.workorder_qty_test_failed = record.workorder_qty_tested
return {
'warning': {
'title': '警告',
'message': '不合格数量不能超过已检数量'
}
}
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,20 +73,59 @@
<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 expr="//field[@name='total_qty']" position="attributes">
<attribute name="attrs">{
'invisible': ['&amp;', '|', ('measure_on', '!=', 'product'), ('is_out_check', '=', False), '|', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False)],
'readonly': [('total_qty_readonly', '=', True)],
'on_change': ['|', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False)]
}</attribute>
</xpath>
<xpath expr="//field[@name='total_qty']" position="after">
<label for="workorder_qty_to_test"
attrs="{'invisible': ['|', ('measure_on', '!=', 'move_line'), '&amp;', ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/>
<div class="o_row"
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="uom_id"/>
</div>
<label for="workorder_qty_tested"
attrs="{'invisible': ['|', ('measure_on', '!=', 'move_line'), '&amp;', ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/>
<div class="o_row"
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="uom_id"/>
</div>
<label for="workorder_qty_test_failed"
attrs="{'invisible': ['|', ('measure_on', '!=', 'move_line'), '&amp;', ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/>
<div class="o_row"
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="uom_id"/>
</div>
</xpath>
<xpath expr="//label[@for='qty_tested']" position="attributes">
<attribute name="attrs">{'invisible': ['|', '|', ('measure_on', '!=', 'move_line'), ('is_lot_tested_fractionally', '=', False), '&amp;', ('measure_on', '=', 'move_line'), ('workorder_id', '!=', False)]}</attribute>
</xpath>
<xpath expr="//div[@class='o_row'][.//field[@name='qty_tested']]" position="attributes">
<attribute name="attrs">{'invisible': ['|', '|', ('measure_on', '!=', 'move_line'), ('is_lot_tested_fractionally', '=', False), '&amp;', ('measure_on', '=', 'move_line'), ('workorder_id', '!=', False)]}</attribute>
</xpath> </xpath>
</field> </field>
</record> </record>

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,9 +63,15 @@ class ReSaleOrder(models.Model):
model_display_version = fields.Char('模型展示版本', default="v1") model_display_version = fields.Char('模型展示版本', default="v1")
contract_code = fields.Char('合同编号')
contract_date = fields.Date('合同日期')
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, remark, state='sale',
model_display_version='v1'): model_display_version='v1'):
now_time = datetime.datetime.now() now_time = datetime.datetime.now()
partner = self.get_customer() partner = self.get_customer()
@@ -84,6 +90,7 @@ class ReSaleOrder(models.Model):
'pay_way': pay_way, 'pay_way': pay_way,
'order_code': order_number, 'order_code': order_number,
'model_display_version': model_display_version, 'model_display_version': model_display_version,
'remark': remark,
} }
if deadline_of_delivery: if deadline_of_delivery:
# deadline_of_delivery字段存在为false字符串情况 # deadline_of_delivery字段存在为false字符串情况
@@ -194,18 +201,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 +227,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 +258,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'
@@ -343,10 +362,13 @@ class RePurchaseOrder(models.Model):
if order_line.product_id.id in product_list: if order_line.product_id.id in product_list:
purchase.purchase_type = 'outsourcing' purchase.purchase_type = 'outsourcing'
break break
request_lines = self.order_line.mapped('purchase_request_lines') if purchase.order_line[0].product_id.categ_id.name == '坯料':
# 检查是否存在 is_subcontract 为 True 的行 if purchase.order_line[0].product_id.materials_type_id.gain_way == '外协':
if any(line.is_subcontract for line in request_lines): purchase.purchase_type = 'outsourcing'
purchase.purchase_type = 'consignment' # request_lines = self.order_line.mapped('purchase_request_lines')
# # 检查是否存在 is_subcontract 为 True 的行
# if any(line.is_subcontract for line in request_lines):
# purchase.purchase_type = 'consignment'
delivery_warning = fields.Selection([('normal', '正常'), ('warning', '预警'), ('overdue', '已逾期')], delivery_warning = fields.Selection([('normal', '正常'), ('warning', '预警'), ('overdue', '已逾期')],
@@ -355,12 +377,13 @@ class RePurchaseOrder(models.Model):
@api.depends('partner_id') @api.depends('partner_id')
def _compute_user_id(self): def _compute_user_id(self):
if not self.user_id: for item in self:
if self.partner_id: if not item.user_id:
self.user_id = self.partner_id.purchase_user_id.id if item.partner_id:
# self.state = 'purchase' item.user_id = item.partner_id.purchase_user_id.id
else: # self.state = 'purchase'
self.user_id = self.env.user.id else:
item.user_id = item.env.user.id
@api.constrains('order_line') @api.constrains('order_line')
def check_order_line(self): def check_order_line(self):
@@ -381,28 +404,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": 1, # "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:
@@ -417,7 +440,7 @@ class RePurchaseOrder(models.Model):
('detailed_type', '=', 'service')]) ('detailed_type', '=', 'service')])
server_product_process.append((0, 0, { server_product_process.append((0, 0, {
'product_id': server_template.product_variant_id.id, 'product_id': server_template.product_variant_id.id,
'product_qty': 1, 'product_qty': production.product_uom_qty,
'product_uom': server_template.uom_id.id, 'product_uom': server_template.uom_id.id,
'related_product': production.product_id.id, 'related_product': production.product_id.id,
'manual_part_number': pp.part_number, 'manual_part_number': pp.part_number,

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

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

@@ -58,20 +58,6 @@ class FunctionalCuttingToolEntity(models.Model):
safe_inventory_id = fields.Many2one('sf.real.time.distribution.of.functional.tools', safe_inventory_id = fields.Many2one('sf.real.time.distribution.of.functional.tools',
string='功能刀具安全库存', readonly=True) string='功能刀具安全库存', readonly=True)
@api.onchange('functional_tool_status')
def _onchange_functional_tool_status(self):
for item in self:
if item:
if item.functional_tool_status == '报警':
self.create_tool_dismantle()
def set_functional_tool_status(self):
# self.write({
# 'functional_tool_status': '报警'
# })
self.functional_tool_status = '报警'
self.create_tool_dismantle()
def create_tool_dismantle(self): def create_tool_dismantle(self):
for item in self: for item in self:
# 创建报警刀具拆解单 # 创建报警刀具拆解单
@@ -150,6 +136,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

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

View File

@@ -42,7 +42,6 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form create="0" edit="0" delete="0"> <form create="0" edit="0" delete="0">
<header> <header>
<button name="set_functional_tool_status" string="报警" type="object" invisible="1"/>
<!-- <button name="enroll_functional_tool_entity" string="功能刀具注册" type="object"--> <!-- <button name="enroll_functional_tool_entity" string="功能刀具注册" type="object"-->
<!-- class="btn-primary"/>--> <!-- class="btn-primary"/>-->
<field name="functional_tool_status" widget="statusbar" statusbar_visible="正常,报警,已拆除"/> <field name="functional_tool_status" widget="statusbar" statusbar_visible="正常,报警,已拆除"/>

View File

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

View File

@@ -107,37 +107,47 @@ class MrsShelfLocationDataSync(models.Model):
equipment_id.register_equipment_tool() equipment_id.register_equipment_tool()
shelfinfo = self.env['sf.shelf.location'].get_sf_shelf_location_info() shelfinfo = self.env['sf.shelf.location'].get_sf_shelf_location_info()
total_data = self.get_total_data() self.set_shelf_location(shelfinfo)
print('shelfinfo:', shelfinfo)
for item in shelfinfo:
logging.info('货架已获取信息:%s' % item)
shelf_barcode = self.find_our_code(total_data, item['Postion'])
location_id = self.env['sf.shelf.location'].search([('barcode', '=', shelf_barcode)], limit=1)
if location_id:
# 如果是线边刀库信息,则对功能刀具移动生成记录
if 'Tool' in item['Postion']:
tool = self.env['sf.functional.cutting.tool.entity'].sudo().search(
[('rfid', '=', item['RfidCode']), ('functional_tool_status', '!=', '已拆除')])
tool.tool_in_out_stock_location(location_id)
if tool:
location_id.product_sn_id = tool.barcode_id.id
# 修改功能刀具状态
if item.get('State') == '报警':
if tool.functional_tool_status != item.get('State'):
tool.write({
'functional_tool_status': item['State']
})
else:
location_id.product_sn_id = False
if item['RfidCode']:
logging.info('Rfid为【%s】的功能刀具在系统中不存在!' % item['RfidCode'])
else:
stock_lot_obj = self.env['stock.lot'].search([('rfid', '=', item['RfidCode'])], limit=1)
if stock_lot_obj:
location_id.product_sn_id = stock_lot_obj.id
else:
location_id.product_sn_id = False
except Exception as e: except Exception as e:
logging.info("库区信息同步失败:%s" % e) logging.info("库区信息同步失败:%s" % e)
raise ValidationError("数据错误导致同步失败,请联系管理员") raise ValidationError("数据错误导致同步失败,请联系管理员")
def set_shelf_location(self, shelfinfo):
print('shelfinfo:', shelfinfo)
total_data = self.get_total_data()
for item in shelfinfo:
logging.info('货架已获取信息:%s' % item)
shelf_barcode = self.env['sf.shelf.location.datasync'].sudo().find_our_code(
total_data, item['Postion'])
location_id = self.env['sf.shelf.location'].sudo().search(
[('barcode', '=', shelf_barcode)],
limit=1)
if location_id:
# 如果是线边刀库信息,则对功能刀具移动生成记录
if 'Tool' in item['Postion']:
tool = self.env['sf.functional.cutting.tool.entity'].sudo().search(
[('rfid', '=', item['RfidCode']), ('functional_tool_status', '!=', '已拆除')])
tool.sudo().tool_in_out_stock_location(location_id)
if tool:
location_id.product_sn_id = tool.barcode_id.id
if item.get('State') == '报警' and tool.functional_tool_status != '报警':
# 创建报警刀具拆解单和刀具报警记录
tool.create_tool_dismantle()
# 修改功能刀具标准状态值和已使用寿命值、功能刀具状态
if 'LifeStd' in item and 'LifeUse' in item:
tool.sudo().write({
'max_lifetime_value': item['LifeStd'],
'used_value': item['LifeUse'],
'functional_tool_status': item['State'],
})
else:
location_id.product_sn_id = False
if item['RfidCode']:
logging.info('Rfid为【%s】的功能刀具在系统中不存在!' % item['RfidCode'])
else:
stock_lot_obj = self.env['stock.lot'].search([('rfid', '=', item['RfidCode'])], limit=1)
if stock_lot_obj:
location_id.product_sn_id = stock_lot_obj.id
else:
location_id.product_sn_id = False