Compare commits

..

304 Commits

Author SHA1 Message Date
胡尧
a2990b8f2e Accept Merge Request #1275: (feature/agv_dispatch -> develop)
Merge Request: 修改agv相关接口请求授权用户

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1275
2024-08-29 20:21:33 +08:00
胡尧
5121e455d2 修改agv相关接口请求授权用户 2024-08-29 20:20:39 +08:00
马广威
4a14fa6bf6 Accept Merge Request #1274: (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/1274
2024-08-29 19:55:09 +08:00
jinling.yang
f251878637 优化工单工序,优化表面工艺排序 2024-08-29 19:53:37 +08:00
jinling.yang
5b3193d3ff Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/new 2024-08-29 19:45:44 +08:00
禹翔辉
52befb6233 Accept Merge Request #1273: (feature/工单优化_2 -> 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/1273
2024-08-29 19:25:22 +08:00
yuxianghui
9ce1963d44 Merge branch 'feature/工单优化_1' into feature/工单优化_2 2024-08-29 19:23:55 +08:00
yuxianghui
1b710b205f 工单优化 2024-08-29 19:23:15 +08:00
禹翔辉
0d253c54c8 Accept Merge Request #1272: (feature/工单优化_1 -> 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/1272
2024-08-29 18:19:22 +08:00
yuxianghui
d2c5fdb509 优化工单 2024-08-29 18:18:19 +08:00
马广威
40209958f2 Accept Merge Request #1271: (feature/优化制造功能 -> develop)
Merge Request: 修复验证代码

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1271?initial=true
2024-08-29 17:53:39 +08:00
mgw
778896a670 修复验证代码 2024-08-29 17:52:56 +08:00
禹翔辉
fcacf609e9 Accept Merge Request #1270: (feature/工单优化_1 -> 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/1270
2024-08-29 17:36:11 +08:00
yuxianghui
5cd134758a Merge branch 'feature/工单工序排序方法优化' into feature/工单优化_1 2024-08-29 17:33:06 +08:00
yuxianghui
b8f8e90444 Merge branch 'feature/刀具组装扫码优化' into feature/工单工序排序方法优化 2024-08-29 17:31:39 +08:00
jinling.yang
b50a852f77 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-29 17:31:38 +08:00
yuxianghui
ea94d74657 优化工单工序排序方法 2024-08-29 17:30:54 +08:00
jinling.yang
b5bea1f811 Merge branch 'feature/修复销售-表面工艺外协' into develop 2024-08-29 17:30:47 +08:00
杨金灵
8416630593 Accept Merge Request #1269: (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/1269
2024-08-29 17:30:32 +08:00
jinling.yang
6572ca25fe 优化提示 2024-08-29 17:30:16 +08:00
jinling.yang
6a7e6ee5c5 修复销售-表面工艺外协 2024-08-29 17:24:09 +08:00
杨金灵
aff35f2dd4 Accept Merge Request #1268: (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/1268
2024-08-29 15:25:11 +08:00
jinling.yang
8ead5655cf 修复外协采购 2024-08-29 15:23:28 +08:00
jinling.yang
d6b3a5a3f1 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-29 14:26:34 +08:00
jinling.yang
19d8f6ae73 Merge branch 'feature/优化工单' into develop 2024-08-29 14:26:24 +08:00
杨金灵
ae8e304d7f Accept Merge Request #1267: (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/1267
2024-08-29 14:25:52 +08:00
jinling.yang
e154f5b763 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化工单
# Conflicts:
#	sf_manufacturing/models/mrp_workorder.py
2024-08-29 14:22:50 +08:00
jinling.yang
804bdb60e8 修复外协采购单 2024-08-29 14:21:18 +08:00
马广威
302636635c Accept Merge Request #1266: (feature/优化制造功能 -> develop)
Merge Request: 翻译持久化

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1266?initial=true
2024-08-28 20:32:23 +08:00
mgw
c465822774 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-08-28 20:31:35 +08:00
mgw
f9063bf3d9 翻译持久化 2024-08-28 20:31:20 +08:00
马广威
ea6fb5e570 Accept Merge Request #1265: (feature/优化制造功能 -> develop)
Merge Request: 修复装夹判断逻辑

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1265?initial=true
2024-08-28 18:08:16 +08:00
mgw
0e672aef79 修复装夹判断逻辑 2024-08-28 18:07:34 +08:00
马广威
11ec04de5b Accept Merge Request #1264: (feature/优化制造功能 -> develop)
Merge Request: 修改装夹提示词

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1264?initial=true
2024-08-28 17:43:15 +08:00
mgw
efe98b5133 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-08-28 17:42:38 +08:00
mgw
2d2abfce25 修改提示词 2024-08-28 17:42:23 +08:00
jinling.yang
821a7a63be 优化工单 2024-08-28 17:30:46 +08:00
廖丹龙
de221ba67e Accept Merge Request #1263: (feature/tax_sync -> 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/1263
2024-08-28 14:55:40 +08:00
liaodanlong
c5bb9a32d0 多面工艺排程问题 2024-08-28 14:50:39 +08:00
杨金灵
925fc2f2b3 Accept Merge Request #1262: (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/1262
2024-08-28 10:42:53 +08:00
jinling.yang
41f379ef5f 优化下发编程单 2024-08-28 10:41:16 +08:00
jinling.yang
acc791978d Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-28 10:39:11 +08:00
jinling.yang
f24c1ab4a5 Merge branch 'feature/修复报废-下发编程单' into develop 2024-08-28 10:31:04 +08:00
杨金灵
179899e483 Accept Merge Request #1261: (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/1261
2024-08-28 10:30:45 +08:00
jinling.yang
b8d21b7fa5 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/修复报废-下发编程单 2024-08-28 10:29:14 +08:00
jinling.yang
295f7ecae3 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-28 10:28:10 +08:00
jinling.yang
d2a76a03d2 修复报废-下发编程单 2024-08-28 10:28:01 +08:00
马广威
4424cb58f4 Accept Merge Request #1260: (feature/优化制造功能 -> develop)
Merge Request: 接口优化

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1260?initial=true
2024-08-27 17:08:35 +08:00
jinling.yang
cd0ea08b21 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-27 17:00:57 +08:00
jinling.yang
10065e95d3 Merge branch 'feature/修复报废-时间' into develop 2024-08-27 17:00:45 +08:00
杨金灵
2c06c6f6a1 Accept Merge Request #1259: (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/1259?initial=true
2024-08-27 17:00:09 +08:00
jinling.yang
1eeaf2f85d Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-27 16:58:41 +08:00
jinling.yang
aa96e63fff 修复报废-时间 2024-08-27 16:58:32 +08:00
胡尧
7ac09653c3 Accept Merge Request #1258: (feature/agv_dispatch -> develop)
Merge Request: 修改日期序列的bug

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1258
2024-08-27 16:49:35 +08:00
胡尧
9c3ed0166b 修改日期序列的bug 2024-08-27 16:49:04 +08:00
廖丹龙
3c8ce870df Accept Merge Request #1257: (feature/tax_sync -> 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/1257?initial=true
2024-08-27 16:38:22 +08:00
liaodanlong
d8e65a20d5 Merge branch 'refs/heads/develop' into feature/tax_sync 2024-08-27 16:34:57 +08:00
liaodanlong
10a622b52d 错误处理 2024-08-27 16:34:27 +08:00
禹翔辉
c1ffd3f870 Accept Merge Request #1255: (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/1255
2024-08-27 16:18:05 +08:00
jinling.yang
a98b7456c0 Merge branch 'feature/修复报废-坯料序列号' into develop 2024-08-27 16:16:01 +08:00
杨金灵
7e3d40bd38 Accept Merge Request #1256: (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/1256
2024-08-27 16:15:12 +08:00
jinling.yang
83d45b6d3f Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/修复报废-坯料序列号 2024-08-27 16:13:06 +08:00
jinling.yang
fe237dc742 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-27 16:12:57 +08:00
jinling.yang
35c13dc51c 修复报废-坯料序列号 2024-08-27 16:12:48 +08:00
yuxianghui
08477e4d94 Merge branch 'feature/收藏无法删除缺陷优化' into feature/刀具组装扫码优化 2024-08-27 14:43:16 +08:00
yuxianghui
069d1f50b0 解决刀具组装时,扫描刚刚拆解完的刀柄状态没有实时更新为可用的问题。 2024-08-27 14:39:49 +08:00
廖丹龙
dd60dee22d Accept Merge Request #1254: (feature/tax_sync -> 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/1254
2024-08-27 14:10:43 +08:00
liaodanlong
e49362e2e3 Merge branch 'refs/heads/develop' into feature/tax_sync 2024-08-27 14:04:13 +08:00
liaodanlong
d5b5231873 多面加工工单排程优化 2024-08-27 14:03:41 +08:00
mgw
2b080c1639 优化完成量接口 2024-08-27 11:43:56 +08:00
jinling.yang
bd3bfc979e Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-27 11:39:48 +08:00
jinling.yang
5a3233d539 Merge branch 'feature/优化表面工艺排序' into develop 2024-08-27 11:39:04 +08:00
杨金灵
3e8f045a40 Accept Merge Request #1253: (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/1253
2024-08-27 11:38:47 +08:00
jinling.yang
d1fab6aab0 还原代码 2024-08-27 11:38:30 +08:00
jinling.yang
a3357e01aa Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化表面工艺排序
# Conflicts:
#	sf_manufacturing/models/stock.py
#	sf_mrs_connect/controllers/controllers.py
2024-08-27 11:34:39 +08:00
jinling.yang
c46a148856 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-27 11:33:33 +08:00
jinling.yang
5794f75f0b Merge branch 'feature/修复报废' into develop 2024-08-27 11:33:04 +08:00
杨金灵
01d9d4a636 Accept Merge Request #1252: (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/1252
2024-08-27 11:32:46 +08:00
jinling.yang
2931d6a92d 去掉不要的代码 2024-08-27 11:32:16 +08:00
jinling.yang
5e6ae1ff55 去掉设置token代码 2024-08-27 11:29:17 +08:00
jinling.yang
d7a0dc578b Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/修复报废
# Conflicts:
#	sf_manufacturing/models/mrp_workorder.py
2024-08-27 11:27:42 +08:00
jinling.yang
44a11c839f 修复下发编程单(报废) 2024-08-27 11:25:12 +08:00
jinling.yang
9808f49b3d Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-27 11:24:13 +08:00
jinling.yang
b40a87df88 修复报废 2024-08-27 11:24:04 +08:00
胡尧
72e9443048 Accept Merge Request #1251: (feature/agv_dispatch -> develop)
Merge Request: 修复工件配送后没有同步任务下发时间的bug,AGV配置中增加是否下发AGV任务

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1251?initial=true
2024-08-27 10:43:44 +08:00
胡尧
c6f6927d57 修复工件配送后没有同步任务下发时间的bug,AGV配置中增加是否下发AGV任务 2024-08-27 10:42:24 +08:00
mgw
ade7588a9c 修改返回数据顺序 2024-08-27 10:23:53 +08:00
mgw
281f03670e Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-08-27 10:20:13 +08:00
mgw
7356e0afb7 接口优化 2024-08-27 10:19:53 +08:00
胡尧
fcd86e230a Accept Merge Request #1250: (feature/update_process_start_time -> 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/1250
2024-08-27 10:05:55 +08:00
guanhuan
bf1271a742 Merge remote-tracking branch 'origin/feature/update_process_start_time' into feature/update_process_start_time 2024-08-27 10:02:31 +08:00
guanhuan
324ed283c4 员工对象内”工作电子邮件“字段变为必填项 2024-08-27 10:01:22 +08:00
hujiaying
ebb8007549 销售订单内”取消“按钮变为”拒绝接单“按钮 2024-08-27 09:46:34 +08:00
胡尧
361f187026 Accept Merge Request #1249: (feature/agv_dispatch -> develop)
Merge Request: 修改人工报价流程

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1249
2024-08-27 09:23:50 +08:00
胡尧
b35f444e49 修改人工报价流程 2024-08-27 09:23:18 +08:00
马广威
9b2a2d644d Accept Merge Request #1248: (feature/优化制造功能 -> develop)
Merge Request: 优化运行信息接口、优化次数接口;增加oee接口

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1248?initial=true
2024-08-27 08:57:22 +08:00
mgw
93ca2000a1 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-08-27 08:56:13 +08:00
胡尧
ca43aa836e Accept Merge Request #1247: (feature/agv_dispatch -> develop)
Merge Request: 修改人工报价流程

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1247?initial=true
2024-08-27 08:55:15 +08:00
mgw
26ad5b5b3f 优化运行信息接口、优化次数接口 2024-08-27 08:55:01 +08:00
胡尧
937ea42af3 修改人工报价流程 2024-08-27 08:54:33 +08:00
jinling.yang
9b957848c2 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/修复报废 2024-08-27 08:53:29 +08:00
jinling.yang
36579d22ac Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-27 08:53:15 +08:00
jinling.yang
e662490cb5 修复报废 2024-08-26 17:31:14 +08:00
mgw
038ce8e139 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-08-26 17:21:02 +08:00
mgw
e1a4784092 增加设备oee数据 2024-08-26 17:20:46 +08:00
禹翔辉
6d4d393b9b Accept Merge Request #1246: (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/1246?initial=true
2024-08-26 17:08:53 +08:00
yuxianghui
c956b06f57 Merge branch 'feature/线边料架接口优化' into feature/收藏无法删除缺陷优化 2024-08-26 17:00:48 +08:00
yuxianghui
da02d68c12 1、将销售单的【取消】改为【拒绝接单】;2、取消功能刀具拆解单的刀柄是否报废按钮; 2024-08-26 16:58:54 +08:00
yuxianghui
eb9b43dc91 解决 【库存概览内增加收藏的默认筛选项后,删除报错(原生报错)】 缺陷 2024-08-26 16:11:40 +08:00
胡嘉莹
ab66e24c9d Accept Merge Request #1244: (feature/update_process_start_time -> 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/1244?initial=true
2024-08-26 15:06:43 +08:00
hujiaying
fbbce6332d 修改表面工艺同步测试,删除同步交期,同步周期菜单及同步接口 2024-08-26 15:02:33 +08:00
马广威
554b86e641 Accept Merge Request #1245: (feature/优化制造功能 -> develop)
Merge Request: 修复coding问题,增加机床类型返回

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1245
2024-08-26 13:25:08 +08:00
mgw
54317a529e Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-08-26 13:24:00 +08:00
mgw
78d00e9157 修复coding问题,增加机床类型返回 2024-08-26 13:23:44 +08:00
hujiaying
89cb61f244 修改同步表面工艺 2024-08-26 12:47:15 +08:00
hujiaying
deef246a6d 修改表面工艺字段同步 2024-08-26 11:10:52 +08:00
hujiaying
9de201705f Merge branch 'develop' into feature/update_process_start_time 2024-08-26 10:35:59 +08:00
廖丹龙
8f8d83d4cb Accept Merge Request #1243: (feature/tax_sync -> 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/1243
2024-08-26 10:34:58 +08:00
liaodanlong
c703012ec4 会计凭证处理 2024-08-26 10:08:40 +08:00
jinling.yang
f1780181fa 修复报废向导 2024-08-25 17:36:07 +08:00
jinling.yang
f5e36f601c 还原注释代码 2024-08-25 15:01:11 +08:00
jinling.yang
6c0b84ec43 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/修复报废 2024-08-25 14:58:57 +08:00
jinling.yang
3e438a10ee 优化表面工艺排序 2024-08-25 14:58:40 +08:00
jinling.yang
9218633a5e 优化表面工艺 2024-08-23 17:32:35 +08:00
hujiaying
c646e70a66 Merge branch 'develop' into feature/update_process_start_time 2024-08-23 16:28:42 +08:00
jinling.yang
40521e06a8 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-23 16:13:35 +08:00
jinling.yang
7fee98cdee 修复报废 2024-08-23 16:13:03 +08:00
马广威
4b7c277a25 Accept Merge Request #1242: (feature/优化制造功能 -> develop)
Merge Request: cnc工单开始二次确认

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1242?initial=true
2024-08-23 12:05:03 +08:00
mgw
064397b32b Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-08-23 12:04:03 +08:00
mgw
048155bc9c Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-08-23 12:03:48 +08:00
马广威
d439c9140d Accept Merge Request #1241: (feature/优化制造功能 -> develop)
Merge Request: 增加异常明细

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1241?initial=true
2024-08-23 11:44:51 +08:00
mgw
296659ebb0 增加异常明细 2024-08-23 11:44:05 +08:00
马广威
a82063d078 Accept Merge Request #1233: (feature/优化制造功能 -> develop)
Merge Request: 增加工单相关接口

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1233
2024-08-23 10:07:41 +08:00
mgw
f17ff2bc57 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-08-23 10:05:30 +08:00
mgw
5df3de7dcd 增加返工数量等 2024-08-23 10:05:17 +08:00
胡尧
b37738fbc3 Accept Merge Request #1240: (feature/agv_dispatch -> develop)
Merge Request: 屏蔽下发agv小车任务

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1240?initial=true
2024-08-22 15:57:49 +08:00
胡尧
79f3dce0ec 屏蔽下发agv小车任务 2024-08-22 15:57:17 +08:00
胡尧
4b87ea3f0e Accept Merge Request #1239: (feature/agv_dispatch -> develop)
Merge Request: 修改下发agv任务逻辑

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1239?initial=true
2024-08-22 15:43:01 +08:00
胡尧
098ac0e7e9 修改下发agv任务逻辑 2024-08-22 15:42:34 +08:00
jinling.yang
6825364a2b Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-22 11:33:53 +08:00
jinling.yang
0cac8fbb4f Merge branch 'feature/修复报废-拣货单(sfp)' into develop 2024-08-22 10:55:12 +08:00
杨金灵
dce748c6a7 Accept Merge Request #1238: (feature/修复报废-拣货单(sfp) -> develop)
Merge Request: 修复报废-拣货单(sfp)

Created By: @杨金灵
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @杨金灵
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1238
2024-08-22 10:54:59 +08:00
jinling.yang
536c766272 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/修复报废-拣货单(sfp) 2024-08-22 10:52:38 +08:00
jinling.yang
422edbcb02 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-22 10:52:28 +08:00
jinling.yang
d13961ff5f 修复报废-拣货单(sfp) 2024-08-22 10:52:19 +08:00
禹翔辉
b603934637 Accept Merge Request #1237: (feature/线边料架接口优化 -> develop)
Merge Request: 1、优化货位信息接口、库位变更接口。

Created By: @禹翔辉
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1237?initial=true
2024-08-22 10:36:34 +08:00
yuxianghui
62d303168e Merge branch 'feature/销售、刀具模块优化_1' into feature/线边料架接口优化 2024-08-22 10:32:47 +08:00
yuxianghui
39bc206344 1、优化货位信息接口、库位变更接口。 2024-08-22 10:31:22 +08:00
胡尧
715d835633 Accept Merge Request #1236: (feature/agv_dispatch -> develop)
Merge Request: 修改接驳站状态接口

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1236
2024-08-22 10:23:52 +08:00
胡尧
9e356682dc 修改接驳站状态接口 2024-08-22 10:22:35 +08:00
胡尧
5dc392a27b Accept Merge Request #1235: (feature/agv_dispatch -> develop)
Merge Request: 修复配送完成修改工件配送单的bug

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1235
2024-08-21 20:19:13 +08:00
胡尧
3d2c62f5db 修复配送完成修改工件配送单的bug 2024-08-21 20:16:41 +08:00
mgw
1e3f0d5ee5 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-08-21 17:44:37 +08:00
mgw
b26919a40d 优化接口,使其易于取值 2024-08-21 17:44:22 +08:00
jinling.yang
9ffe338532 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-21 17:00:01 +08:00
jinling.yang
08d333e4e2 Merge branch 'feature/修复报废-工单状态' into develop 2024-08-21 16:59:29 +08:00
杨金灵
47c501c6c6 Accept Merge Request #1234: (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/1234
2024-08-21 16:59:13 +08:00
jinling.yang
335c79e618 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-21 16:54:11 +08:00
jinling.yang
ba986995ed 修复报废-工单状态 2024-08-21 16:53:32 +08:00
马广威
2a45cdd53a Accept Merge Request #1232: (feature/agv_dispatch -> 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/1232#mr-1232-review-178956
2024-08-21 16:25:05 +08:00
胡尧
0cdf37bb50 修改接口名字 2024-08-21 16:20:45 +08:00
mgw
bae2592587 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-08-21 15:38:21 +08:00
mgw
c86cc27510 增加工单相关接口 2024-08-21 15:38:05 +08:00
胡尧
2dded43e39 Accept Merge Request #1231: (feature/agv_dispatch -> develop)
Merge Request: 开启agv任务下发代码

Created By: @胡尧
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1231
2024-08-21 15:10:07 +08:00
胡尧
eb6e638018 开启agv任务下发代码 2024-08-21 15:06:06 +08:00
jinling.yang
f64c21dacd Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-21 14:50:12 +08:00
jinling.yang
426aa78ed4 Merge branch 'feature/修复报废bug' into develop 2024-08-21 14:49:59 +08:00
杨金灵
42c65496ff Accept Merge Request #1230: (feature/修复报废bug -> 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/1230
2024-08-21 14:47:23 +08:00
jinling.yang
e893abd83e 还原接口代码 2024-08-21 14:46:37 +08:00
jinling.yang
78e2e7184e Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/修复报废bug 2024-08-21 14:43:44 +08:00
jinling.yang
ce575adc88 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-21 14:43:33 +08:00
jinling.yang
6668e0ee2f 还原OCC代码 2024-08-21 14:43:22 +08:00
胡尧
647d21dea3 Accept Merge Request #1229: (feature/agv_dispatch -> develop)
Merge Request: 放开下发agv小车任务代码

Created By: @胡尧
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1229
2024-08-21 11:32:50 +08:00
胡尧
0e13e2190e 屏蔽agv任务下发代码 2024-08-21 11:31:09 +08:00
胡尧
7f3d50a130 开启agv小车任务下发代码 2024-08-21 10:03:03 +08:00
jinling.yang
7b2908defa 修复报废bug 2024-08-20 17:52:36 +08:00
胡尧
a8211171b0 增加以日、月、年为日期期间的序列,工件配送单状态同步,返工时不生成工件配送单 2024-08-20 16:18:47 +08:00
yuxianghui
c57daa2c52 1、处理销售订单确认接单后,确认接单、取消按钮没有隐藏问题 2024-08-20 16:13:30 +08:00
jinling.yang
4ed7ecf628 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-20 14:45:39 +08:00
jinling.yang
8a6ebb331a Merge branch 'feature/优化最新版报废' into develop 2024-08-20 14:39:21 +08:00
杨金灵
b333e27c51 Accept Merge Request #1228: (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/1228
2024-08-20 14:39:03 +08:00
jinling.yang
6761851407 还原代码 2024-08-20 14:38:42 +08:00
胡尧
ada7936d1b Accept Merge Request #1227: (feature/agv_dispatch -> develop)
Merge Request: 增加可以移除焦点的controller

Created By: @胡尧
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1227
2024-08-20 14:36:43 +08:00
胡尧
9c40aa9a7e 修复更新agv站点同时下发任务的bug 2024-08-20 14:36:02 +08:00
jinling.yang
0694541653 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-20 14:34:34 +08:00
jinling.yang
e4e2dca22c 还原注释代码 2024-08-20 14:34:25 +08:00
jinling.yang
e259fd0c05 优化报废 2024-08-20 14:30:42 +08:00
胡尧
a767c6491f 增加移除焦点的controller 2024-08-20 14:05:05 +08:00
马广威
6c2045d0c3 Accept Merge Request #1222: (feature/优化制造功能 -> develop)
Merge Request: 运行日志数据接入;大屏对接

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1222
2024-08-20 11:28:33 +08:00
mgw
6fe56605ab Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-08-20 11:27:34 +08:00
mgw
bf2c7fd3c5 增加解绑托盘功能 2024-08-20 11:27:18 +08:00
胡尧
54fc91baa2 去掉js中的日志 2024-08-20 10:50:55 +08:00
黄焱
376f2b3079 Accept Merge Request #1226: (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/1226?initial=true
2024-08-20 10:50:39 +08:00
hy
3c50f19e2d 保存、取消按钮增加文字提示 2024-08-20 10:48:42 +08:00
胡尧
08622b10b9 Accept Merge Request #1225: (feature/agv_dispatch -> develop)
Merge Request: 生产总监、机床操作岗增加agv调度菜单权限,AGV下发任务界面需二次确认

Created By: @胡尧
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1225
2024-08-20 10:48:35 +08:00
胡尧
dc5b68cca0 生产总监、机床操作岗增加agv调度菜单权限,AGV下发任务界面需二次确认 2024-08-20 10:45:31 +08:00
mgw
1d399527e0 增加装夹预调工单扫码开启 2024-08-20 10:42:36 +08:00
禹翔辉
b3bfa69656 Accept Merge Request #1224: (feature/销售、刀具模块优化_1 -> 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/1224?initial=true
2024-08-20 09:05:57 +08:00
yuxianghui
96e226836e Merge branch 'feature/销售、刀具模块优化' into feature/销售、刀具模块优化_1 2024-08-20 09:02:52 +08:00
yuxianghui
062ca66328 1、库位变更接口、库位信息接口优化;2、功能刀具模型from页面按钮样式及显示优化; 2024-08-19 17:29:09 +08:00
胡尧
7ab8269088 Accept Merge Request #1223: (feature/agv_dispatch -> develop)
Merge Request: 修复agv调度bug

Created By: @胡尧
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1223
2024-08-19 14:40:04 +08:00
胡尧
1241737dea 修改agv调度搜索条件 2024-08-19 14:37:55 +08:00
mgw
813e424ec9 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-08-19 14:25:43 +08:00
mgw
8c08893aa1 修复“制造订单详情多了一个没有文案的按钮” 2024-08-19 14:25:26 +08:00
胡尧
5ad35de76b 修复agv调度bug 2024-08-19 11:22:30 +08:00
jinling.yang
3c8dac799d 修复报废 2024-08-16 17:28:21 +08:00
mgw
29c1c7a54d 调整日志顺序;增加订单名返回 2024-08-16 17:19:54 +08:00
胡尧
2c27a9b575 Accept Merge Request #1221: (feature/agv_dispatch -> develop)
Merge Request: 装夹工单完成后生成工件配送单同时绑定rfid

Created By: @胡尧
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1221
2024-08-16 17:13:46 +08:00
胡尧
0e4b9e44f8 修改下发agv调度任务参数 2024-08-16 17:12:34 +08:00
mgw
a07a1ba268 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-08-16 16:57:47 +08:00
mgw
f537fbc597 运行日志数据接入 2024-08-16 16:57:32 +08:00
胡尧
a6b7167f3b 修改创建调度任务逻辑,不匹配的路线返回错误信息 2024-08-16 16:49:30 +08:00
胡尧
c4de966dea 装夹工单完成后生成工件配送单同时绑定rfid 2024-08-16 16:31:54 +08:00
yuxianghui
03cfc00be5 Merge branch 'feature/销售单优化' into feature/销售、刀具模块优化 2024-08-16 16:02:19 +08:00
yuxianghui
cc05e8423e 1、注释重写的销售单交货按钮代码 2024-08-16 16:01:24 +08:00
yuxianghui
e78516f73c 1、 完成 功能刀具组装和拆解页面扫描操作优化需求 2024-08-16 15:59:24 +08:00
胡尧
a277bb402e Accept Merge Request #1220: (feature/agv_dispatch -> 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/1220?initial=true
2024-08-16 15:24:27 +08:00
胡尧
e5793638f7 屏蔽开始装夹预调绑定rfid时,不绑定工件配送的rfid 2024-08-16 15:21:08 +08:00
胡尧
9673fd165d Accept Merge Request #1219: (feature/agv_dispatch -> develop)
Merge Request: agv任务调度功能

Created By: @胡尧
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1219?initial=true
2024-08-16 10:41:30 +08:00
胡尧
e92905fe32 修改agv小车相关接口 2024-08-16 10:36:01 +08:00
胡尧
1b521ae460 解决冲突 2024-08-16 09:50:31 +08:00
胡尧
4e0d8f1c88 修改AGV调度 2024-08-16 09:43:03 +08:00
胡尧
aecf2121a1 agv调度开发 2024-08-15 17:41:25 +08:00
jinling.yang
e2d7576a5e 优化报废 2024-08-15 17:31:37 +08:00
mgw
cc13e5cd9a Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-08-15 17:14:40 +08:00
mgw
271f34a03b 增加开动率采集 2024-08-15 17:14:07 +08:00
yuxianghui
366e816268 1、优化货位变更接口、优化同步库存信息接口;2、隐藏已取消状态的销售订单的【确认接单】和【取消】按钮;3、处理功能刀具从线边刀库到刀具房没有生成移动历史问题; 2024-08-15 16:45:39 +08:00
胡尧
1532184008 Merge branch 'feature/agv_dispatch' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/agv_dispatch 2024-08-15 15:02:16 +08:00
胡尧
9f180e307d agv调度开发 2024-08-15 15:02:13 +08:00
hy
f0a4cf1d0f 弹窗取消聚焦 2024-08-15 15:01:10 +08:00
胡尧
6c734eead4 agv调度开发 2024-08-15 11:34:38 +08:00
胡尧
f7e4ce416a AGV任务调度开发 2024-08-14 17:34:50 +08:00
hujiaying
5a60eed5b1 联调加工订单从待确认到加工中,经过的内部管理系统,bfm,sf接口测试联调,修改对应接收的参数值改变。采购订单加工中同步到内部管理系统失败,则新增到同步表,由同步表定时发起接口重试 2024-08-14 12:48:40 +08:00
yuxianghui
bf34de58fc 1、优化功能刀具预警记录跳转功能刀具拆解单链接方法;2、优化功能刀具预警界面及搜索等方法;3、优化拆解单功能及界面;4、处理功能刀具组装时,刀柄状态不实时变更问题 2024-08-13 17:30:19 +08:00
jinling.yang
cccc2f8493 优化报废 2024-08-12 17:30:53 +08:00
yuxianghui
894d3b9ea3 1、功能刀具拆解单添加功能刀具唯一校验;2、功能刀具模型tree视图的状态字段根据不同状态添加不同颜色;当功能刀具状态为报警时,自动创建拆解单和预警记录,并添加拆解单链接按钮;添加tree视图按状态排序,将报警状态刀具记录显示在最前面;3、优化功能刀具预警模型字段及关联关系,添加预警记录跳转到对应由该功能刀具生成的拆解单链接; 2024-08-12 17:25:56 +08:00
胡尧
0b85f29262 修改agv调度 2024-08-12 11:08:04 +08:00
胡尧
9dbea66b73 修改AGV调度系统 2024-08-09 17:32:46 +08:00
jinling.yang
9a7ac4dfa6 优化制造订单报废向导 2024-08-09 17:26:57 +08:00
yuxianghui
e6ca4c27ac 1、库位变更接口、库位信息接口优化;2、功能刀具预警模型优化; 2024-08-09 17:19:03 +08:00
jinling.yang
a8560a0684 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化最新版报废 2024-08-09 15:45:11 +08:00
jinling.yang
4f74996f24 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-09 15:44:48 +08:00
jinling.yang
5b26abc203 Merge branch 'feature/优化表面工艺外协' into develop 2024-08-09 15:43:52 +08:00
杨金灵
49654f1ff6 Accept Merge Request #1218: (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/1218
2024-08-09 15:43:33 +08:00
jinling.yang
a9b7f99944 修复表面工艺外协 2024-08-09 15:39:58 +08:00
jinling.yang
34e858ffe4 添加表面工艺服务产品验证 2024-08-09 11:31:30 +08:00
jinling.yang
41efe81119 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化表面工艺外协 2024-08-09 10:43:34 +08:00
jinling.yang
7cab8cd287 调拨单新增表面工艺参数字段 2024-08-09 10:42:59 +08:00
jinling.yang
5f9c5961a5 报废 2024-08-09 10:39:48 +08:00
胡尧
a79500d0ad 增加agv调度系统 2024-08-08 17:47:03 +08:00
jinling.yang
4399700c3d 优化报废向导 2024-08-08 17:24:41 +08:00
yuxianghui
36fd17b6c7 1、处理 智能工厂,销售角色与采购角色权限更新 需求 2024-08-08 17:03:36 +08:00
杨金灵
9d52466f61 Accept Merge Request #1217: (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/1217?initial=true
2024-08-08 16:24:06 +08:00
jinling.yang
112efccb7c 修复表面工艺外协采购 2024-08-08 16:21:15 +08:00
jinling.yang
45b9177da4 优化工单 2024-08-08 16:09:00 +08:00
jinling.yang
745658eff2 1.制造订单添加报废状态,
2.返工操作制造订单添加过滤条件:状态为报废
3.cnc工单完成检测结果添加报废
2024-08-08 15:40:29 +08:00
jinling.yang
ce8b0127b8 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-08 15:08:17 +08:00
jinling.yang
f58fb2ea13 Merge branch 'feature/优化表面工艺' into develop 2024-08-08 15:08:08 +08:00
杨金灵
d333621c7a Accept Merge Request #1216: (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/1216
2024-08-08 15:07:37 +08:00
jinling.yang
563023fa3c 中控接口身份验证放开 2024-08-08 15:07:22 +08:00
jinling.yang
3d2c7425c4 还原代码 2024-08-08 15:03:45 +08:00
jinling.yang
aaa19b96fd 还原注释代码 2024-08-08 14:57:55 +08:00
胡尧
5d8f0f83b2 增加AGV调度功能,工单页面增加自定义筛选字段 2024-08-07 17:44:43 +08:00
jinling.yang
1547a6064f 优化表面工艺 2024-08-07 17:30:29 +08:00
禹翔辉
55b2daa0a3 Accept Merge Request #1215: (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/1215
2024-08-07 17:13:57 +08:00
yuxianghui
d469c09f15 Merge branch 'feature/制造订单优化' into feature/销售单优化 2024-08-07 17:11:57 +08:00
yuxianghui
a03cb31b29 1、处理 SF销售单需求优化 需求 2024-08-07 17:11:12 +08:00
廖丹龙
d2b20761b5 Accept Merge Request #1214: (feature/tax_sync -> develop)
Merge Request: bom原材料查询出现多条会导致bfm 分配工厂时报错

Created By: @廖丹龙
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1214
2024-08-07 09:49:20 +08:00
liaodanlong
0f47b4b8c3 bom原材料查询限制只拿一条 2024-08-07 09:44:04 +08:00
liaodanlong
0cfb16b9d9 Merge remote-tracking branch 'origin/develop' into develop 2024-08-06 09:09:59 +08:00
廖丹龙
8ec5e05739 Accept Merge Request #1213: (feature/tax_sync -> develop)
Merge Request: bfm 加工订单税信息同步

Created By: @廖丹龙
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1213
2024-08-06 09:09:37 +08:00
jinling.yang
5393ef686a 优化表面工艺 2024-08-05 17:28:48 +08:00
yuxianghui
e92c9675b6 1、 处理 内部调拨单,在选择目标货位时,隐藏带循环货位标签的货位不进行展示 2024-08-05 17:18:54 +08:00
liaodanlong
e6bad80031 Merge branch 'refs/heads/feature/tax_sync' into develop 2024-08-05 17:12:37 +08:00
yuxianghui
070890e3b6 解决 SF,根据制造订单生成的采购单坯料入库时到原材料库BUG 2024-08-05 16:26:54 +08:00
mgw
6e50774b23 调整机床列表接口位置;增加机床品牌型号返回 2024-08-05 16:23:27 +08:00
mgw
fc18d34495 增加大屏用获取cnc机床列表接口 2024-08-05 16:04:53 +08:00
jinling.yang
ed0d57c364 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化表面工艺
# Conflicts:
#	sf_manufacturing/models/mrp_production.py
#	sf_manufacturing/models/mrp_workorder.py
2024-08-02 14:41:42 +08:00
禹翔辉
3f27ae0f35 Accept Merge Request #1212: (feature/bug优化 -> develop)
Merge Request: 修改夹具重量单位为kg

Created By: @禹翔辉
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1212
2024-08-02 11:22:06 +08:00
yuxianghui
cec426920b Merge branch 'feature/优化工单状态' into feature/bug优化 2024-08-02 11:19:45 +08:00
mgw
09a6255d9c 去掉无用文件夹及未用po 2024-08-02 09:57:52 +08:00
yuxianghui
a5da18bbc7 1、修改夹具重量单位为kg 2024-08-01 11:34:20 +08:00
liaodanlong
510e8d49fb 功能刀具拆解失败问题 2024-08-01 10:46:14 +08:00
liaodanlong
051273017d Merge remote-tracking branch 'refs/remotes/origin/develop' into feature/tax_sync 2024-08-01 10:45:09 +08:00
yuxianghui
f49557090a 1、优化刀具拆解单拆解时生成的调拨单单据验证出现数据缺失问题 2024-08-01 10:40:25 +08:00
liaodanlong
115a3e0712 Merge remote-tracking branch 'refs/remotes/origin/develop' into feature/tax_sync 2024-07-31 14:41:02 +08:00
liaodanlong
d49e8779f9 Merge remote-tracking branch 'refs/remotes/origin/develop' into feature/tax_sync 2024-07-30 09:24:48 +08:00
liaodanlong
81dbbf980c Merge remote-tracking branch 'refs/remotes/origin/develop' into feature/tax_sync 2024-07-29 15:09:37 +08:00
jinling.yang
39214e5352 注释代码 2024-07-28 11:47:12 +08:00
jinling.yang
36a2bcca6e 优化表面工yi 2024-07-26 15:49:25 +08:00
jinling.yang
19c4b99bae 创建制造订单后生成询价单 2024-07-25 17:21:44 +08:00
jinling.yang
33fdd0f051 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化表面工艺 2024-07-25 15:44:01 +08:00
jinling.yang
2deaffb4eb 添加表面工艺产品校验 2024-07-25 14:07:36 +08:00
liaodanlong
8996297521 Merge branch 'refs/heads/master' into feature/tax_sync 2024-07-25 11:43:01 +08:00
jinling.yang
0d1cb49cb7 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化表面工艺 2024-07-25 10:35:52 +08:00
马广威
8840e9642d Accept Merge Request #1173: (release/release_2.1 -> master)
Merge Request: 1、货架、货位添加循环货位标签;2、功能刀具拆解时选择的货位添加只能为循环货位的过滤条件

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1173?initial=true
2024-07-24 20:23:31 +08:00
jinling.yang
96c22a5d46 添加表面工艺服务产品验证 2024-07-24 17:30:59 +08:00
jinling.yang
121861863f 优化销售订单确认方法 2024-07-24 16:57:50 +08:00
jinling.yang
c2c8d63848 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化表面工艺 2024-07-24 16:41:19 +08:00
jinling.yang
a5d8e88f1d 注释OCC代码 2024-07-24 16:33:47 +08:00
liaodanlong
ef1c7b6b25 多余字符串信息 2024-07-19 17:23:01 +08:00
liaodanlong
03fe730c50 产品添加税信息 2024-07-19 16:46:53 +08:00
liaodanlong
a0d3b40548 添加工厂订单和产品创建时的错误日志 2024-07-19 16:46:31 +08:00
liaodanlong
dfba055019 添加bfm同步时产品创建填充税值 2024-07-19 16:45:46 +08:00
92 changed files with 3604 additions and 6809 deletions

View File

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

View File

@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
{
'name': "jikimo_account_process",
'summary': """
Short (1 phrase/line) summary of the module's purpose, used as
subtitle on modules listing or apps.openerp.com""",
'description': """
Long description of module's purpose
""",
'author': "My Company",
'website': "https://www.yourcompany.com",
# Categories can be used to filter modules in modules listing
# Check https://github.com/odoo/odoo/blob/16.0/odoo/addons/base/data/ir_module_category_data.xml
# for the full list
'category': 'Uncategorized',
'version': '0.1',
# any module necessary for this one to work correctly
'depends': ['base', 'account'],
# always loaded
'data': [
# 'security/ir.model.access.csv',
# 'views/views.xml',
# 'views/templates.xml',
],
# only loaded in demonstration mode
'demo': [
# 'demo/demo.xml',
],
}

View File

@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import controllers

View File

@@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# from odoo import http
# class JikimoAccountProcess(http.Controller):
# @http.route('/jikimo_account_process/jikimo_account_process', auth='public')
# def index(self, **kw):
# return "Hello, world"
# @http.route('/jikimo_account_process/jikimo_account_process/objects', auth='public')
# def list(self, **kw):
# return http.request.render('jikimo_account_process.listing', {
# 'root': '/jikimo_account_process/jikimo_account_process',
# 'objects': http.request.env['jikimo_account_process.jikimo_account_process'].search([]),
# })
# @http.route('/jikimo_account_process/jikimo_account_process/objects/<model("jikimo_account_process.jikimo_account_process"):obj>', auth='public')
# def object(self, obj, **kw):
# return http.request.render('jikimo_account_process.object', {
# 'object': obj
# })

View File

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

View File

@@ -0,0 +1,15 @@
from odoo import models, fields, api
from odoo.exceptions import ValidationError
class CustomAccountMoveLine(models.Model):
_inherit = 'account.move'
_description = "account move line"
@api.model_create_multi
def create(self, vals):
for val in vals:
val['name'] = self.env['ir.sequence'].next_by_code('account.move') or '/'
# 因为供应商与客户支付创建流程是先创建move line在修改来填充account_payment与move line的关联
return super(CustomAccountMoveLine, self).create(vals)

View File

@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
# from odoo import models, fields, api
# class jikimo_account_process(models.Model):
# _name = 'jikimo_account_process.jikimo_account_process'
# _description = 'jikimo_account_process.jikimo_account_process'
# name = fields.Char()
# value = fields.Integer()
# value2 = fields.Float(compute="_value_pc", store=True)
# description = fields.Text()
#
# @api.depends('value')
# def _value_pc(self):
# for record in self:
# record.value2 = float(record.value) / 100

View File

@@ -53,6 +53,23 @@ const tableRequiredList = [
]
patch(FormStatusIndicator.prototype, 'jikimo_frontend.FormStatusIndicator', {
setup() {
owl.onMounted(() => {
try {
const dom = this.__owl__.bdom.el
const buttonsDom = $(dom).find('.o_form_status_indicator_buttons ')
if (buttonsDom) {
const dom1 = buttonsDom.children('.o_form_button_save')
const dom2 = buttonsDom.children('.o_form_button_cancel')
dom1.append('保存')
dom2.append('取消')
}
} catch (e) {
console.log(e)
}
});
},
// 你可以重写或者添加一些方法和属性
async _onDiscardChanges() {
// var self = this;
@@ -183,17 +200,6 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', {
// })
$(function () {
document.addEventListener('click', function () {
const dom = $('.o_form_status_indicator_buttons ')
if (dom) {
const dom1 = dom.children().eq(0)
const dom2 = dom.children().eq(1)
if (!dom1.text()) {
dom1.append('保存')
dom2.append('取消')
}
}
})
function customRequired() {
let timer = null

View File

@@ -324,4 +324,4 @@ def unlink(self):
BaseModel._create = _create
BaseModel.unlink = unlink
# BaseModel.unlink = unlink

View File

@@ -35,6 +35,7 @@
],
'web.assets_backend': [
'sf_base/static/src/scss/*.scss',
'sf_base/static/src/js/*.js',
],
},

View File

@@ -5,3 +5,4 @@ from . import fixture
from . import functional_fixture
from . import tool_other_features
from . import basic_parameters_fixture
from . import ir_sequence

View File

@@ -13,7 +13,7 @@ class BasicParametersFixture(models.Model):
diameter = fields.Float('直径(mm)', digits=(16, 2))
# '零点卡盘' 字段
weight = fields.Float('重量(mm)', digits=(16, 2))
weight = fields.Float('重量(kg)', digits=(16, 2))
orientation_dish_diameter = fields.Float('定位盘直径(mm)', digits=(16, 2))
clamping_diameter = fields.Float('装夹直径(mm)', digits=(16, 2))
clamping_num = fields.Selection([('1', '1'), ('2', '2'), ('4', '4'), ('6', '6'), ('8', '8')], string='装夹单元数')

View File

@@ -84,10 +84,12 @@ class MrsProductionProcessCategory(models.Model):
class MrsProductionProcess(models.Model):
_name = 'sf.production.process'
_description = '表面工艺'
order = 'sequence asc'
code = fields.Char("编码")
name = fields.Char('名称')
remark = fields.Text("备注")
sequence = fields.Integer('排序')
# processing_order_ids = fields.One2many('sf.processing.order', 'production_process_id', string='工序')
partner_process_ids = fields.Many2many('res.partner', 'process_ids', '加工工厂')
active = fields.Boolean('有效', default=True)
@@ -96,7 +98,7 @@ class MrsProductionProcess(models.Model):
# workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_process', required=True)
processing_day = fields.Float('加工天数/d')
travel_day = fields.Float('路途天数/d')
sequence = fields.Integer('排序')
# class MrsProcessingTechnology(models.Model):
# _name = 'sf.processing.technology'
@@ -148,6 +150,7 @@ class MrsProductionProcessParameter(models.Model):
processing_day = fields.Float('加工天数/d')
travel_day = fields.Float('路途天数/d')
active = fields.Boolean('有效', default=True)
processing_mm = fields.Char('加工厚度/mm')
def name_get(self):
result = []

View File

@@ -0,0 +1,74 @@
import calendar
from datetime import timedelta
from odoo import models, fields
class IrSequence(models.Model):
_inherit = 'ir.sequence'
date_range_period = fields.Selection(
[('day', '每日'), ('month', '每月'), ('year', '每年')],
string='日期期间',
)
def _next(self, sequence_date=None):
""" Returns the next number in the preferred sequence in all the ones given in self."""
if not self.use_date_range:
return self._next_do()
# date mode
dt = sequence_date or self._context.get('ir_sequence_date', fields.Date.today())
seq_date = self.env['ir.sequence.date_range'].search(
[
('sequence_id', '=', self.id),
('date_from', '<=', dt),
('date_to', '>=', dt),
('date_range_period', '=', self.date_range_period)
], limit=1)
if not seq_date:
if self.date_range_period:
seq_date = self._create_date_range_seq_by_period(dt, self.date_range_period)
else:
seq_date = self._create_date_range_seq(dt)
return seq_date.with_context(ir_sequence_date_range=seq_date.date_from)._next()
def _create_date_range_seq_by_period(self, date, period):
if period == 'year':
year = fields.Date.from_string(date).strftime('%Y')
date_from = '{}-01-01'.format(year)
date_to = '{}-12-31'.format(year)
if period == 'month':
# 计算当前月份的第一天和最后一天
year = fields.Date.from_string(date).strftime('%Y')
month = fields.Date.from_string(date).strftime('%m')
date_from = fields.Date.from_string(date).strftime('%Y-%m-01')
date_to = '{}-{}-{}'.format(year, month, calendar.monthrange(int(year), int(month))[1])
if period == 'day':
date_from = date
date_to = date
date_range = self.env['ir.sequence.date_range'].search(
[
('sequence_id', '=', self.id),
('date_to', '>=', date_from),
('date_to', '<=', date),
('date_range_period', '=', period)
],
order='date_to desc', limit=1)
if date_range:
date_from = date_range.date_to + timedelta(days=1)
seq_date_range = self.env['ir.sequence.date_range'].sudo().create({
'date_from': date_from,
'date_to': date_to,
'sequence_id': self.id,
'date_range_period': period,
})
return seq_date_range
class IrSequenceDateRange(models.Model):
_inherit = 'ir.sequence.date_range'
date_range_period = fields.Selection(
[('day', '每日'), ('month', '每月'), ('year', '每年')],
string='日期期间',
)

View File

@@ -1,10 +0,0 @@
diff a/sf_base/models/tool_base_new.py b/sf_base/models/tool_base_new.py (rejected hunks)
@@ -108,6 +108,4 @@
cutting_speed_ids = fields.One2many('sf.cutting.speed', 'standard_library_id', string='切削速度Vc')
- feed_per_tooth_ids = fields.One2many('sf.feed.per.tooth', 'standard_library_id', '每齿走刀量fz',
- domain=[('cutting_speed', '!=', False)])
- feed_per_tooth_ids_3 = fields.One2many('sf.feed.per.tooth', 'standard_library_id', '每齿走刀量fz',
- domain=[('cutting_speed', '!=', False)])
+ feed_per_tooth_ids = fields.One2many('sf.feed.per.tooth', 'standard_library_id', '每齿走刀量fz')
+ feed_per_tooth_ids_3 = fields.One2many('sf.feed.per.tooth', 'standard_library_id', '每齿走刀量fz')

View File

@@ -0,0 +1,23 @@
/** @odoo-module **/
import { registry } from '@web/core/registry';
import { formView } from '@web/views/form/form_view';
import { FormController } from '@web/views/form/form_controller';
import { onRendered, onMounted } from "@odoo/owl";
export class RemoveFocusController extends FormController {
setup() {
super.setup();
onMounted(() => {
this.__owl__.bdom.el.querySelectorAll(':focus').forEach(element => element.blur());
})
}
}
registry.category('views').add('remove_focus_view', {
...formView,
Controller: RemoveFocusController,
});

View File

@@ -16,7 +16,7 @@
<record model="ir.ui.view" id="mrs_production_process_parameter_form">
<field name="model">sf.production.process.parameter</field>
<field name="arch" type="xml">
<form string="表面工艺可选参数" create="0" delete="0">
<form string="表面工艺可选参数" create="0" delete="0" >
<sheet>
<div class="oe_title">
<h1>
@@ -33,11 +33,12 @@
<group>
<field name="processing_day" readonly="1"/>
<field name="travel_day" readonly="1"/>
<field name="processing_mm" readonly="1"/>
</group>
</group>
<notebook>
<page string="适用材料">
<field name="materials_model_ids"></field>
<field name="materials_model_ids" readonly="1"></field>
</page>
</notebook>
</sheet>
@@ -52,7 +53,7 @@
<search>
<filter name="filter_active" string="已归档" domain="[('active','=',False)]"/>
<field name="name" string="名称" filter_domain="[('name','ilike',self)]"/>
<field name="code" string="编码" filter_domain="[('codeNum','ilike',self)]"/>
<field name="code" string="编码" filter_domain="[('code','ilike',self)]"/>
<searchpanel class="account_root">
<field name="process_id" icon="fa-filter"/>
</searchpanel>
@@ -140,7 +141,7 @@
<field name="model">sf.production.process.category</field>
<field name="arch" type="xml">
<tree string="表面工艺类别" default_order="sequence, id" create="0" edit="0" delete="1">
<field name="sequence" widget="handle" string="序号"/>
<field name="sequence" widget="handle" string="序号" readonly="1"/>
<field name="code"/>
<field name="name" string="名称"/>
</tree>
@@ -163,7 +164,8 @@
<record model="ir.ui.view" id="sf_production_process_tree">
<field name="model">sf.production.process</field>
<field name="arch" type="xml">
<tree string="表面工艺" create="0" edit="0" delete="1">
<tree string="表面工艺" create="0" edit="0" delete="0">
<field name="sequence" string="加工顺序" readonly="1"/>
<field name="code"/>
<field name="name" string="名称"/>
<field name="remark"/>
@@ -174,7 +176,7 @@
<record model="ir.ui.view" id="sf_production_process_form">
<field name="model">sf.production.process</field>
<field name="arch" type="xml">
<form string="表面工艺" create="0" edit="1" delete="1">
<form string="表面工艺" create="0" delete="0">
<sheet>
<div class="oe_title">
<h1>
@@ -192,11 +194,11 @@
</group>
</group>
<notebook>
<page string="可选参数">
<field name="parameter_ids">
<tree force_save="1">
<page string="可选参数" >
<field name="parameter_ids" >
<tree force_save="1" create="0">
<field name="code" readonly="1" force_save="1"/>
<field name="name"/>
<field name="name" readonly="1"/>
<field name="gain_way"/>
<field name='process_id' default="default"/>
</tree>

View File

@@ -171,7 +171,7 @@
<field name="width"/>
<field name="height"/>
<field name="diameter"/>
<field name="weight"/>
<field name="weight" string="重量(kg)"/>
<field name="orientation_dish_diameter"/>
<field name="clamping_diameter"/>
<field name="clamping_num"/>
@@ -197,7 +197,7 @@
<field name="width"/>
<field name="height"/>
<field name="diameter"/>
<field name="weight"/>
<field name="weight" string="重量(kg)"/>
<field name="clamping_diameter"/>
<field name="connector_diameter"/>
<field name="chucking_power_max"/>
@@ -220,7 +220,7 @@
<field name="length"/>
<field name="width"/>
<field name="height"/>
<field name="weight"/>
<field name="weight" string="重量(kg)"/>
<field name="gripper_length_min"/>
<field name="gripper_width_min"/>
<field name="gripper_height_min"/>
@@ -248,7 +248,7 @@
<field name="length"/>
<field name="width"/>
<field name="height"/>
<field name="weight"/>
<field name="weight" string="重量(kg)"/>
<field name="gripper_length_min"/>
<field name="gripper_width_min"/>
<field name="gripper_height_min"/>
@@ -278,7 +278,7 @@
<field name="width"/>
<field name="height"/>
<field name="height_tolerance_value"/>
<field name="weight"/>
<field name="weight" string="重量(kg)"/>
<field name="gripper_length_min"/>
<field name="gripper_width_min"/>
<field name="gripper_height_min"/>
@@ -307,7 +307,7 @@
<field name="length"/>
<field name="width"/>
<field name="height"/>
<field name="weight"/>
<field name="weight" string="重量(kg)"/>
<field name="gripper_length_min"/>
<field name="gripper_width_min"/>
<field name="gripper_height_min"/>
@@ -335,7 +335,7 @@
<field name="width"/>
<field name="height"/>
<field name="diameter"/>
<field name="weight"/>
<field name="weight" string="重量(kg)"/>
<field name="gripper_length_min"/>
<field name="gripper_width_min"/>
<field name="gripper_height_min"/>

View File

@@ -36,7 +36,7 @@ class Http(models.AbstractModel):
post_time = int(datas['HTTP_TIMESTAMP'])
datetime_post = datetime.fromtimestamp(post_time)
datetime_now = datetime.now().replace(microsecond=0)
datetime_del = datetime_now + timedelta(seconds=5)
datetime_del = datetime_now + timedelta(seconds=30)
if datetime_post > datetime_del:
raise AuthenticationError('请求已过期')
check_str = '%s%s%s' % (datas['HTTP_TOKEN'], post_time, factory_secret.sf_secret_key)

View File

@@ -1,7 +1,8 @@
from datetime import datetime
import logging
import requests
from odoo import fields, models
from odoo.exceptions import UserError
from odoo import fields, models, _
_logger = logging.getLogger(__name__)
@@ -14,26 +15,49 @@ class StatusChange(models.Model):
def action_confirm(self):
# 在原有方法执行前记录日志和执行其他操作
logging.info('函数已经执行=============')
server_product_none = []
for order in self.order_line:
gain_way_no = order.product_template_id.model_process_parameters_ids.filtered(lambda a: not a.gain_way)
if gain_way_no:
process_parameters = [item.name for item in gain_way_no]
raise UserError(
_("请先至【制造】-【配置】中【表面工艺可选参数】为【%s】填写获取方式", ", ".join(process_parameters)))
for item in order.product_template_id.model_process_parameters_ids:
if item.gain_way == '外协':
server_product = self.env['product.template'].search(
[('server_product_process_parameters_id', '=', item.id),
('detailed_type', '=', 'service')])
if not server_product:
server_product_none.append(item.name)
if server_product_none:
raise UserError(_("请先至【产品】中创建【表面工艺参数】为【%s】的服务产品", ", ".join(server_product_none)))
# 使用super()来调用原始方法(在本例中为'sale.order'模型的'action_confirm'方法)
res = super(StatusChange, self).action_confirm()
# 原有方法执行后进行额外的操作如调用外部API
process_start_time = str(datetime.now())
config = self.env['res.config.settings'].get_values()
json1 = {
'params': {
'model_name': 'jikimo.process.order',
'field_name': 'name',
'default_code': self.default_code,
'state': '加工中',
'process_start_time': process_start_time,
},
}
url1 = config['bfm_url_new'] + '/api/get/state/get_order'
requests.post(url1, json=json1, data=None)
logging.info('接口已经执行=============')
try:
res = super(StatusChange, self).action_confirm()
# 原有方法执行后进行额外的操作如调用外部API
process_start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
config = self.env['res.config.settings'].get_values()
json1 = {
'params': {
'model_name': 'jikimo.process.order',
'field_name': 'name',
'default_code': self.default_code,
'state': '加工中',
'process_start_time': process_start_time,
},
}
url1 = config['bfm_url_new'] + '/api/get/state/get_order'
ret = requests.post(url1, json=json1, data=None)
ret = ret.json()
if not ret.get('error'):
logging.info('接口已经执行=============')
else:
logging.error('工厂加工同步订单状态失败 {}'.format(ret.text))
raise UserError('工厂加工同步订单状态失败')
except UserError as e:
logging.error('工厂加工同步订单状态失败 {}'.format(e))
raise UserError('工厂加工同步订单状态失败')
return res
def action_cancel(self):
@@ -202,12 +226,12 @@ class FinishStatusChange(models.Model):
[('id', 'child_of', self.picking_type_id.warehouse_id.view_location_id.id),
('usage', '!=', 'supplier')])
if self.env['stock.move'].search([
('state', 'in', ['confirmed', 'partially_available', 'waiting', 'assigned']),
('product_qty', '>', 0),
('location_id', 'in', wh_location_ids),
('move_orig_ids', '=', False),
('picking_id', 'not in', self.ids),
('product_id', 'in', lines.product_id.ids)], limit=1):
('state', 'in', ['confirmed', 'partially_available', 'waiting', 'assigned']),
('product_qty', '>', 0),
('location_id', 'in', wh_location_ids),
('move_orig_ids', '=', False),
('picking_id', 'not in', self.ids),
('product_id', 'in', lines.product_id.ids)], limit=1):
action = self.action_view_reception_report()
action['context'] = {'default_picking_ids': self.ids}
return action

View File

@@ -122,7 +122,7 @@ class ResMrpBomMo(models.Model):
# 查bom的原材料
def get_raw_bom(self, product):
raw_bom = self.env['product.product'].search(
[('categ_id.type', '=', '原材料'), ('materials_type_id', '=', product.materials_type_id.id)])
[('categ_id.type', '=', '原材料'), ('materials_type_id', '=', product.materials_type_id.id)],limit=1)
return raw_bom

3
sf_hr/__init__.py Normal file
View File

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

22
sf_hr/__manifest__.py Normal file
View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': '机企猫智能工厂 员工管理',
'version': '1.0',
'summary': '智能工厂员工模块',
'sequence': 1,
'category': 'sf',
'website': 'https://www.sf.jikimo.com',
'depends': ['hr'],
'data': [
'views/hr_employee.xml',
],
'demo': [
],
'qweb': [
],
'license': 'LGPL-3',
'installable': True,
'application': False,
'auto_install': False,
}

2
sf_hr/models/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-

View File

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<data>
<record id="view_form_employee_extend" model="ir.ui.view">
<field name="name">employee_form</field>
<field name="model">hr.employee</field>
<field name="inherit_id" ref="hr.view_employee_form"/>
<field name="arch" type="xml">
<xpath expr="//group//field[@name='work_email']" position="attributes">
<attribute name="required">1</attribute>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@@ -30,6 +30,7 @@
'views/machine_info_present.xml',
'views/delivery_record.xml',
'views/res_config_settings_views.xml',
'views/maintenance_views.xml',
],
'assets': {

View File

@@ -1,10 +1,55 @@
# -*- coding: utf-8 -*-
import re
import ast
import json
import base64
import logging
import psycopg2
from datetime import datetime, timedelta
from odoo import http
from odoo.http import request
# 数据库连接配置
db_config = {
"database": "timeseries_db",
"user": "postgres",
"password": "postgres",
"port": "5432",
"host": "172.16.10.98"
}
def convert_to_seconds(time_str):
# 修改正则表达式,使 H、M、S 部分可选
pattern = r"(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?"
match = re.match(pattern, time_str)
if match:
# 提取各时间单位如果某个单位缺失则默认设为0
hours = int(match.group(1)) if match.group(1) else 0
minutes = int(match.group(2)) if match.group(2) else 0
seconds = int(match.group(3)) if match.group(3) else 0
# 计算总秒数
total_seconds = hours * 3600 + minutes * 60 + seconds
if total_seconds == 0:
# return None
pattern = r"(?:(\d+)小时)?(?:(\d+)分钟)?(?:(\d+)秒)?"
match = re.match(pattern, time_str)
if match:
# 提取各时间单位如果某个单位缺失则默认设为0
hours = int(match.group(1)) if match.group(1) else 0
minutes = int(match.group(2)) if match.group(2) else 0
seconds = int(match.group(3)) if match.group(3) else 0
# 计算总秒数
total_seconds = hours * 3600 + minutes * 60 + seconds
return total_seconds
else:
return None
return total_seconds
class Sf_Dashboard_Connect(http.Controller):
@@ -18,6 +63,11 @@ class Sf_Dashboard_Connect(http.Controller):
"""
res = {'status': 1, 'message': '成功', 'data': []}
logging.info('前端请求机床数据的参数为:%s' % kw)
# 获取当前时间的时间戳
current_timestamp = datetime.now().timestamp()
print(current_timestamp)
# tem_list = [
# "XT-GNJC-WZZX-X800-Y550-Z550-T24-A5-1", "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-3",
# "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-4", "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-5",
@@ -33,10 +83,24 @@ class Sf_Dashboard_Connect(http.Controller):
machine_list = ast.literal_eval(kw['machine_list'])
for item in machine_list:
machine_data = equipment_obj.search([('code', '=', item)])
# 机床上线时间段
first_online_duration = current_timestamp - int(machine_data.first_online_time.timestamp())
power_off_time = None
power_off_rate = None
if machine_data.machine_power_on_time:
power_off_time = first_online_duration - convert_to_seconds(machine_data.machine_power_on_time)
power_off_rate = round((power_off_time / first_online_duration), 3)
else:
power_off_time = False
power_off_rate = False
if machine_data:
res['data'].append({
'active': machine_data.status,
'id': machine_data.id,
'name': machine_data.name,
'brand': machine_data.type_id.name,
'code': machine_data.code,
'status': machine_data.status,
'run_status': machine_data.run_status,
@@ -88,11 +152,681 @@ class Sf_Dashboard_Connect(http.Controller):
'alarm_time': machine_data.alarm_time,
'alarm_msg': machine_data.alarm_msg,
'clear_time': machine_data.clear_time,
# 计算出来的数据
# 开动率:运行时间/通电时间
'run_rate': machine_data.run_rate,
# 关机时长:初次上线时间 - 通电时间
'power_off_time': power_off_time,
# 关机率:关机时长/初次上线时间
'power_off_rate': power_off_rate,
'first_online_duration': first_online_duration,
# 停机时间:关机时间 - 运行时间
# 停机时长:关机时间 - 初次上线时间
'img': f'data:image/png;base64,{machine_data.machine_tool_picture.decode("utf-8")}',
'equipment_type': machine_data.category_id.name,
})
return json.JSONEncoder().encode(res)
return json.dumps(res)
except Exception as e:
logging.info('前端请求机床数据失败,原因:%s' % e)
res['status'] = -1
res['message'] = '前端请求机床数据失败,原因:%s' % e
return json.JSONEncoder().encode(res)
@http.route('/api/logs/list', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
def logs_list(self, **kw):
"""
拿到日志数据返回给大屏展示
:param kw:
:return:
"""
res = {'status': 1, 'message': '成功', 'data': {}}
logging.info('前端请求日志数据的参数为:%s' % kw)
try:
# 连接数据库
conn = psycopg2.connect(**db_config)
cur = conn.cursor()
machine_list = ast.literal_eval(kw['machine_list'])
begin_time_str = kw['begin_time'].strip('"')
end_time_str = kw['end_time'].strip('"')
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
print('begin_time: %s' % begin_time)
for item in machine_list:
sql = '''
SELECT time, device_state, program_name
FROM device_data
WHERE device_name = %s AND time >= %s AND time <= %s
ORDER BY time DESC;
'''
# 执行SQL命令使用参数绑定
cur.execute(sql, (item, begin_time, end_time))
results = cur.fetchall()
# 将数据按照 equipment_code 进行分组
if item not in res['data']:
res['data'][item] = []
for result in results:
res['data'][item].append({
'time': result[0].strftime('%Y-%m-%d %H:%M:%S'),
'state': result[1],
'production_name': result[2],
})
return json.dumps(res) # 注意使用 json.dumps 而不是直接用 json.JSONEncoder().encode()
except Exception as e:
logging.info('前端请求日志数据失败,原因:%s' % e)
res['status'] = -1
res['message'] = '前端请求日志数据失败,原因:%s' % e
return json.dumps(res)
# 返回CNC机床列表
@http.route('/api/CNCList', type='http', auth='public', methods=['GET', 'POST'], csrf=False,
cors="*")
def CNCList(self, **kw):
"""
获取CNC机床列表
:param kw:
:return:
"""
# logging.info('CNCList:%s' % kw)
try:
res = {'Succeed': True}
# cnc_list = request.env['sf.cnc.equipment'].sudo().search([])
# cnc_list = ["XT-GNJC-WZZX-X800-Y550-Z550-T24-A5-1", "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-3",
# "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-4", "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-5",
# "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-6", "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-7",
# "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-8", "XT-GNJC-WZZX-X800-Y550-Z550-T24-A5-2",
# "XT-GNJC-GSZG-X600-Y400-Z350-T21-A3-9", "XT-GNJC-GSZG-X600-Y400-Z350-T21-A3-10",
# "XT-GNJC-GSZG-X600-Y400-Z350-T21-A3-11", "XT-GNJC-GSZG-X600-Y400-Z350-T21-A3-12",
# "XT-GNJC-GSZG-X600-Y400-Z350-T21-A3-13", "XT-GNJC-GSZG-X600-Y400-Z350-T21-A3-14"]
cnc_list_obj = request.env['maintenance.equipment'].sudo().search(
[('function_type', '!=', False), ('active', '=', True)])
cnc_list = list(map(lambda x: x.code, cnc_list_obj))
print('cnc_list: %s' % cnc_list)
res['CNCList'] = cnc_list
except Exception as e:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
logging.info('CNCList error:%s' % e)
return json.JSONEncoder().encode(res)
# 返回产线列表
@http.route('/api/LineList', type='http', auth='public', methods=['GET', 'POST'], csrf=False,
cors="*")
def LineList(self, **kw):
"""
获取产线列表
:param kw:
:return:
"""
try:
res = {'Succeed': True}
line_list_obj = request.env['sf.production.line'].sudo().search([('name', 'ilike', 'CNC')])
line_list = list(map(lambda x: x.name, line_list_obj))
print('line_list: %s' % line_list)
res['LineList'] = line_list
except Exception as e:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
logging.info('LineList error:%s' % e)
return json.JSONEncoder().encode(res)
# 获取产线产量相关
@http.route('/api/LineProduct', type='http', auth='public', methods=['GET', 'POST'], csrf=False,
cors="*")
def LineProduct(self, **kw):
"""
获取产线产量相关
:param kw:
:return:
"""
res = {'status': 1, 'message': '成功', 'data': {}}
logging.info('前端请求产线产量数据的参数为:%s' % kw)
try:
plan_obj = request.env['sf.production.plan'].sudo()
line_list = ast.literal_eval(kw['line_list'])
print('line_list: %s' % line_list)
for line in line_list:
plan_data = plan_obj.search([('production_line_id.name', '=', line)])
# 工单总量
plan_data_total_counts = plan_obj.search_count([('production_line_id.name', '=', line)])
# 工单完成量
plan_data_finish_counts = plan_obj.search_count(
[('production_line_id.name', '=', line), ('state', 'in', ['finished'])])
# 工单计划量
plan_data_plan_counts = plan_obj.search_count(
[('production_line_id.name', '=', line), ('state', 'not in', ['finished'])])
# 工单不良累计
plan_data_fault_counts = plan_obj.search_count(
[('production_line_id.name', '=', line), ('production_id.state', 'in', ['scrap', 'cancel'])])
# 工单返工数量
plan_data_rework_counts = plan_obj.search_count(
[('production_line_id.name', '=', line), ('production_id.state', 'in', ['rework'])])
# 工单完成率
finishe_rate = round(
(plan_data_finish_counts / plan_data_total_counts if plan_data_total_counts > 0 else 0), 3)
# 工单进度偏差
plan_data_progress_deviation = plan_data_finish_counts - plan_data_plan_counts
if plan_data:
data = {
'plan_data_total_counts': plan_data_total_counts,
'plan_data_finish_counts': plan_data_finish_counts,
'plan_data_plan_counts': plan_data_plan_counts,
'plan_data_fault_counts': plan_data_fault_counts,
'finishe_rate': finishe_rate,
'plan_data_progress_deviation': plan_data_progress_deviation,
'plan_data_rework_counts': plan_data_rework_counts
}
res['data'][line] = data
return json.dumps(res) # 注意使用 json.dumps 而不是直接用 json.JSONEncoder().encode()
except Exception as e:
logging.info('前端请求产线产量数据失败,原因:%s' % e)
res['status'] = -1
res['message'] = '前端请求产线产量数据失败,原因:%s' % e
return json.dumps(res)
# 日完成量统计
@http.route('/api/DailyFinishCount', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
def DailyFinishCount(self, **kw):
"""
获取日完成量统计
:param kw:
:return:
"""
res = {'status': 1, 'message': '成功', 'data': {}}
plan_obj = request.env['sf.production.plan'].sudo()
line_list = ast.literal_eval(kw['line_list'])
begin_time_str = kw['begin_time'].strip('"')
end_time_str = kw['end_time'].strip('"')
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
print('line_list: %s' % line_list)
def get_date_list(start_date, end_date):
date_list = []
current_date = start_date
while current_date <= end_date:
date_list.append(current_date)
current_date += timedelta(days=1)
return date_list
for line in line_list:
date_list = get_date_list(begin_time, end_time)
order_counts = []
date_field_name = 'actual_end_time' # 替换为你模型中的实际字段名
for date in date_list:
next_day = date + timedelta(days=1)
orders = plan_obj.search([('production_line_id.name', '=', line), ('state', 'in', ['finished']),
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
])
rework_orders = plan_obj.search(
[('production_line_id.name', '=', line), ('production_id.state', 'in', ['rework']),
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
])
not_passed_orders = plan_obj.search(
[('production_line_id.name', '=', line), ('production_id.state', 'in', ['scrap', 'cancel']),
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
])
order_counts.append({
'date': date.strftime('%Y-%m-%d'),
'order_count': len(orders),
'rework_orders': len(rework_orders),
'not_passed_orders': len(not_passed_orders)
})
# 外面包一层没什么是包一层不能解决的包一层就能区分了类似于包一层div
# 外面包一层的好处是,可以把多个数据结构打包在一起,方便前端处理
# date_list_dict = {line: order_counts}
res['data'][line] = order_counts
return json.dumps(res)
# 实时产量
@http.route('/api/RealTimeProduct', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
def RealTimeProduct(self, **kw):
"""
获取实时产量
:param kw:
:return:
"""
res = {'status': 1, 'message': '成功', 'data': {}}
plan_obj = request.env['sf.production.plan'].sudo()
line_list = ast.literal_eval(kw['line_list'])
begin_time_str = kw['begin_time'].strip('"')
end_time_str = kw['end_time'].strip('"')
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
def get_hourly_intervals(start_time, end_time):
intervals = []
current_time = start_time
while current_time < end_time:
next_hour = current_time + timedelta(hours=1)
intervals.append((current_time, min(next_hour, end_time)))
current_time = next_hour
return intervals
# 当班计划量
for line in line_list:
plan_order_nums = plan_obj.search_count(
[('production_line_id.name', '=', line), ('state', 'not in', ['draft']),
('date_planned_start', '>=', begin_time),
('date_planned_start', '<', end_time)
])
finish_order_nums = plan_obj.search_count(
[('production_line_id.name', '=', line), ('state', 'in', ['finished']),
('date_planned_start', '>=', begin_time),
('date_planned_start', '<', end_time)
])
hourly_intervals = get_hourly_intervals(begin_time, end_time)
production_counts = []
for start, end in hourly_intervals:
orders = plan_obj.search([
('actual_end_time', '>=', start.strftime('%Y-%m-%d %H:%M:%S')),
('actual_end_time', '<', end.strftime('%Y-%m-%d %H:%M:%S')),
('production_line_id.name', '=', line)
])
production_counts.append({
'start_time': start.strftime('%Y-%m-%d %H:%M:%S'),
'end_time': end.strftime('%Y-%m-%d %H:%M:%S'),
'production_count': len(orders)
})
production_counts_dict = {'production_counts': production_counts,
'plan_order_nums': plan_order_nums,
'finish_order_nums': finish_order_nums,
}
res['data'][line] = production_counts_dict
# res['data'].append({line: production_counts})
return json.dumps(res)
# 工单明细
@http.route('/api/OrderDetail', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
def OrderDetail(self, **kw):
"""
获取工单明细
:param kw:
:return:
"""
# res = {'status': 1, 'message': '成功', 'not_done_data': [], 'done_data': []}
res = {'status': 1, 'message': '成功', 'data': {}}
plan_obj = request.env['sf.production.plan'].sudo()
line_list = ast.literal_eval(kw['line_list'])
begin_time_str = kw['begin_time'].strip('"')
end_time_str = kw['end_time'].strip('"')
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
print('line_list: %s' % line_list)
not_done_data = []
done_data = []
final_data = {}
for line in line_list:
# 未完成订单
not_done_orders = plan_obj.search(
[('production_line_id.name', '=', line), ('state', 'not in', ['finished'])])
print(not_done_orders)
# 完成订单
finish_orders = plan_obj.search([('production_line_id.name', '=', line), ('state', 'in', ['finished'])])
print(finish_orders)
# 获取所有未完成订单的ID列表
order_ids = [order.id for order in not_done_orders]
# 获取所有已完成订单的ID列表
finish_order_ids = [order.id for order in finish_orders]
# 对ID进行排序
sorted_order_ids = sorted(order_ids)
finish_sorted_order_ids = sorted(finish_order_ids)
# 创建ID与序号的对应关系
id_to_sequence = {order_id: index + 1 for index, order_id in enumerate(sorted_order_ids)}
finish_id_to_sequence = {order_id: index + 1 for index, order_id in enumerate(finish_sorted_order_ids)}
# # 输出结果或进一步处理
# for order_id, sequence in id_to_sequence.items():
# print(f"Order ID: {order_id} - Sequence: {sequence}")
for order in not_done_orders:
blank_name = ''
try:
blank_name = order.production_id.move_raw_ids[0].product_id.name
except:
continue
# blank_name = 'R-S00109-1 [碳素结构钢 Q235-118.0 * 72.0 * 21.0]'
# 正则表达式
material_pattern = r'\[(.*?)-' # 从 [ 开始,碰到 - 停止
dimensions = blank_name.split('-')[-1].split(']')[0]
# 匹配材料名称
material_match = re.search(material_pattern, blank_name)
material = material_match.group(1) if material_match else 'No match found'
state_dict = {
'draft': '待排程',
'done': '已排程',
'processing': '生产中',
'finished': '已完成'
}
line_dict = {
'sequence': id_to_sequence[order.id],
'workorder_name': order.name,
'blank_name': blank_name,
'material': material,
'dimensions': dimensions,
'order_qty': order.product_qty,
'state': state_dict[order.state],
}
not_done_data.append(line_dict)
for finish_order in finish_orders:
blank_name = ''
try:
blank_name = finish_order.production_id.move_raw_ids[0].product_id.name
except:
continue
material_pattern = r'\[(.*?)-' # 从 [ 开始,碰到 - 停止
dimensions = blank_name.split('-')[-1].split(']')[0]
# 匹配材料名称
material_match = re.search(material_pattern, blank_name)
material = material_match.group(1) if material_match else 'No match found'
line_dict = {
'sequence': finish_id_to_sequence[finish_order.id],
'workorder_name': finish_order.name,
'blank_name': blank_name,
'material': material,
'dimensions': dimensions,
'order_qty': finish_order.product_qty,
'finish_time': finish_order.actual_end_time.strftime('%Y-%m-%d %H:%M:%S'),
}
done_data.append(line_dict)
# 开始包一层
res['data'][line] = {'not_done_data': not_done_data, 'done_data': done_data}
return json.dumps(res)
# 查询pg库来获得待机次数
@http.route('/api/IdleAlarmCount', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
def idle_alarm_count(self, **kw):
"""
查询设备的待机次数
"""
res = {'status': 1, 'message': '成功', 'data': {}}
logging.info('前端请求机床数据的参数为:%s' % kw)
# 连接数据库
conn = psycopg2.connect(**db_config)
cur = conn.cursor()
try:
# 获取请求的机床数据
machine_list = ast.literal_eval(kw['machine_list'])
total_alarm_time = 0
alarm_count_num = 0
for item in machine_list:
sql = '''
SELECT COUNT(*)
FROM (
SELECT DISTINCT ON (idle_start_time) idle_start_time
FROM device_data
WHERE device_name = %s AND idle_start_time IS NOT NULL
ORDER BY idle_start_time, time
) subquery;
'''
sql2 = '''
SELECT DISTINCT ON (alarm_time) alarm_time, alarm_repair_time
FROM device_data
WHERE device_name = %s AND alarm_time IS NOT NULL
ORDER BY alarm_time, time;
'''
# 执行SQL命令
cur.execute(sql, (item,))
result = cur.fetchall()
print('result========', result)
cur.execute(sql2, (item,))
result2 = cur.fetchall()
print('result2========', result2)
#
for row in result:
res['data'][item] = {'idle_count': row[0]}
alarm_count = []
for row in result2:
alarm_count.append(row[0])
total_alarm_time += abs(float(row[0]))
if len(list(set(alarm_count))) == 1:
if list(set(alarm_count))[0] is None:
alarm_count_num = 0
else:
alarm_count_num = 1
else:
alarm_count_num = len(list(set(alarm_count)))
res['data'][item]['total_alarm_time'] = total_alarm_time / 3600
res['data'][item]['alarm_count_num'] = alarm_count_num
# 返回统计结果
return json.dumps(res)
except Exception as e:
print(f"An error occurred: {e}")
return json.dumps(res)
finally:
cur.close()
conn.close()
# 查询pg库来获得异常情况
@http.route('/api/alarm/logs', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
def alarm_logs(self, **kw):
"""
查询设备的异常情况
"""
res = {'status': 1, 'message': '成功', 'data': {}}
logging.info('前端请求机床数据的参数为:%s' % kw)
# 连接数据库
conn = psycopg2.connect(**db_config)
cur = conn.cursor()
try:
# 获取请求的机床数据
# machine_list = ast.literal_eval(kw['machine_list'])
# idle_times = []
# idle_dict = {}
# for item in machine_list:
sql = '''
SELECT DISTINCT ON (alarm_time) alarm_time, alarm_message, system_date, system_time, alarm_repair_time
FROM device_data
WHERE alarm_time IS NOT NULL
ORDER BY alarm_time, time;
'''
# 执行SQL命令
cur.execute(sql)
result = cur.fetchall()
print('result', result)
# 将查询结果转换为字典列表
data = []
for row in result:
record = {
'alarm_time': row[0],
'alarm_message': row[1],
'system_date': row[2],
'system_time': row[3],
'alarm_repair_time': row[4]
}
data.append(record)
# 将数据填充到返回结果中
res['data'] = data
# 返回统计结果
return json.dumps(res, ensure_ascii=False)
except Exception as e:
print(f"An error occurred: {e}")
return json.dumps(res)
finally:
cur.close()
conn.close()
# 设备oee
@http.route('/api/OEE', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
def OEE(self, **kw):
"""
获取产线等oee
"""
res = {'status': 1, 'message': '成功', 'data': {}}
logging.info('前端请求oee数据的参数为:%s' % kw)
try:
count_oee = 1
workcenter_obj = request.env['mrp.workcenter'].sudo()
workcenter_list = ast.literal_eval(kw['workcenter_list'])
print('workcenter_list: %s' % workcenter_list)
for line in workcenter_list:
res['data'][line] = workcenter_obj.search([('name', '=', line)]).oee
count_oee *= workcenter_obj.search([('name', '=', line)]).oee
res['data']['综合oee'] = count_oee / 1000000
except Exception as e:
print(f"An error occurred: {e}")
return json.dumps(res)
# # 查询某段时间的设备oee
# @http.route('/api/OEEByTime', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
# def OEEByTime(self, **kw):
# """
# 获取某段时间的oee
# """
# res = {'status': 1, 'message': '成功', 'data': {}}
# logging.info('前端请求获取某段时间的oee的参数为:%s' % kw)
# workcenter_list = ast.literal_eval(kw['workcenter_list'])
# begin_time_str = kw['begin_time'].strip('"')
# end_time_str = kw['end_time'].strip('"')
# begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
# end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
# print('workcenter_list: %s' % workcenter_list)
# # 连接数据库
# conn = psycopg2.connect(**db_config)
# cur = conn.cursor()
# # 查询并计算OEE平均值
# oee_data = {}
# for workcenter in workcenter_list:
# cur.execute("""
# SELECT AVG(oee) as avg_oee
# FROM oee_data
# WHERE workcenter_name = %s
# AND time BETWEEN %s AND %s
# """, (workcenter, begin_time, end_time))
#
# result = cur.fetchone()
# avg_oee = result[0] if result else 0.0
# oee_data[workcenter] = avg_oee
#
# # 返回数据
# res['data'] = oee_data
# return json.dumps(res)
@http.route('/api/OEEByTime', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
def OEEByTime(self, **kw):
"""
获取某段时间的OEE根据用户指定的时间单位day或hour返回对应的平均值。
如果不传time_unit则默认按天返回并补全没有数据的时间段填充0值。
"""
res = {'status': 1, 'message': '成功', 'data': {}}
logging.info('前端请求获取某段时间的OEE的参数为:%s' % kw)
# 获取并解析参数
workcenter_list = ast.literal_eval(kw['workcenter_list'])
begin_time_str = kw['begin_time'].strip('"')
end_time_str = kw['end_time'].strip('"')
time_unit = kw.get('time_unit', 'day') # 默认单位为天
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
# 连接数据库
conn = psycopg2.connect(**db_config)
cur = conn.cursor()
# 根据时间单位选择不同的时间格式
if time_unit == 'hour':
time_format = 'YYYY-MM-DD HH24:00:00'
time_delta = timedelta(hours=1)
else: # 默认为'day'
time_format = 'YYYY-MM-DD'
time_delta = timedelta(days=1)
# 查询并计算OEE平均值
oee_data = {}
for workcenter in workcenter_list:
cur.execute(f"""
SELECT to_char(time, '{time_format}') as time_unit, AVG(oee) as avg_oee
FROM oee_data
WHERE workcenter_name = %s
AND time BETWEEN %s AND %s
GROUP BY time_unit
ORDER BY time_unit
""", (workcenter, begin_time, end_time))
results = cur.fetchall()
# 初始化当前产线的OEE数据字典
workcenter_oee = {row[0]: row[1] for row in results}
# 补全缺失的时间段
current_time = begin_time
if time_unit != 'hour':
while current_time <= end_time:
time_key = current_time.strftime('%Y-%m-%d')
if time_key not in workcenter_oee:
workcenter_oee[time_key] = 0
current_time += time_delta
# 按时间排序
oee_data[workcenter] = dict(sorted(workcenter_oee.items()))
# 关闭数据库连接
cur.close()
conn.close()
# 返回数据
res['data'] = oee_data
return json.dumps(res)

View File

@@ -2,3 +2,4 @@ from . import ftp_client
from . import ftp_operate
from . import py2opcua
from . import res_config_setting
from . import mrp_workorder

View File

@@ -121,6 +121,13 @@ class Machine_ftp(models.Model):
"""
_inherit = 'maintenance.equipment'
# 机床首次上线时间(默认取值2024年08月01日零点)
def _get_default_online_time(self):
return datetime(2024, 1, 1, 0, 0, 0)
first_online_time = fields.Datetime(string='首次上线时间', default=_get_default_online_time)
# workorder_ids = fields.One2many('mrp.workorder', 'machine_tool_id', string='工单')
# # 机床配置项目
@@ -275,7 +282,28 @@ class Machine_ftp(models.Model):
alarm_msg = fields.Char('故障报警信息', readonly=True)
clear_time = fields.Char('故障消除时间(复原时间)', readonly=True)
# 当前程序名, 机床累计运行时间, 机床系统日期, 机床系统时间, 当前刀具号, 机床循环时间
# # 开动率
run_rate = fields.Char('开动率', readonly=True)
# 同步CNC设备到oee
def sync_oee(self):
"""
同步CNC设备到oee
:return:
"""
for record in self:
record.ensure_one()
cnc_oee_dict = {
'equipment_id': record.id,
'type_id': record.type_id.id,
'machine_tool_picture': record.machine_tool_picture,
'equipment_code': record.code,
'function_type': record.function_type,
}
if self.env['maintenance.equipment.oee.logs'].search([('equipment_id', '=', record.id)]):
self.env['maintenance.equipment.oee.logs'].write(cnc_oee_dict)
else:
self.env['maintenance.equipment.oee.logs'].create(cnc_oee_dict)
class WorkCenterBarcode(models.Model):

View File

@@ -0,0 +1,38 @@
import re
from odoo import fields, models, api
class ResMrpWorkOrder(models.Model):
_inherit = 'mrp.workorder'
mixed_search_field = fields.Char(string='坯料产品名称/RFID')
@api.model
def web_read_group(self, domain, fields, groupby, limit=None, offset=0, orderby=False,
lazy=True, expand=False, expand_limit=None, expand_orderby=False):
domain = domain or []
for index, item in enumerate(domain):
if isinstance(item, list):
if item[0] == 'mixed_search_field':
if self._is_rfid_code(item[2]):
domain[index] = ['rfid_code', item[1], item[2]]
else:
domain[index] = ['product_tmpl_name', item[1], item[2]]
return super(ResMrpWorkOrder, self).web_read_group(domain, fields, groupby, limit=limit, offset=offset, orderby=orderby,
lazy=lazy, expand=expand, expand_limit=expand_limit, expand_orderby=expand_orderby)
def _is_rfid_code(self, tag):
"""
判断是否是rfid_code
"""
# 基于长度判断假设RFID标签长度为10到16个字符
if not 10 <= len(tag) <= 16:
return False
# 基于字符集判断(仅包含数字和字母)
if not re.match("^[0-9]*$", tag):
return False
return True

View File

@@ -26,6 +26,7 @@
<filter string="自动编程" name="no_manual_quotation" domain="[('manual_quotation', '=', False)]"/>
</xpath>
<xpath expr="//field[@name='production_id']" position="before">
<field name="mixed_search_field"/>
<field name="product_tmpl_name"/>
<field name="rfid_code"/>
</xpath>

View File

@@ -18,6 +18,7 @@
<field name="run_status"/>
<field name="run_time"/>
<field name="system_date"/>
<field name="first_online_time"/>
</group>
<group>
<field name="cut_status"/>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0"?>
<odoo>
<!-- 修改设备列表视图-->
<record id="sf_machine_hr_equipment_view_tree_inherit" model="ir.ui.view">
<field name="name">sf.machine.hr.equipment.view.tree.inherit</field>
<field name="model">maintenance.equipment</field>
<field name="inherit_id" ref="maintenance.hr_equipment_view_tree"/>
<field name="arch" type="xml">
<xpath expr="//tree" position="inside">
<header>
<button name="sync_oee" type="object" string="同步设备至OEE"/>
</header>
</xpath>
</field>
</record>
</odoo>

View File

@@ -41,29 +41,32 @@ class SfMaintenanceEquipmentOEELog(models.Model):
_name = 'maintenance.equipment.oee.logs'
_description = '设备运行日志'
equipment_id = fields.Many2one('maintenance.equipment', '机台号')
equipment_code = fields.Char('设备编码')
equipment_id = fields.Many2one('maintenance.equipment', '机台号', readonly='True')
equipment_code = fields.Char('设备编码', readonly='True')
name = fields.Char('设备名称', readonly='True')
function_type = fields.Selection(
[("ZXJGZX", "钻铣加工中心"), ("CXJGZX", "车削加工中心"), ("FHJGZX", "复合加工中心")],
default="", string="功能类型")
machine_tool_picture = fields.Binary('设备图片')
type_id = fields.Many2one('sf.machine_tool.type', '品牌型号')
type_id = fields.Many2one('sf.machine_tool.type', '品牌型号', reaonly='True')
state = fields.Selection([("加工", "加工"), ("关机", "关机"), ("待机", "待机"), ("故障", "故障"),
("检修", "检修"), ("保养", "保养")], default="", string="实时状态")
online_time = fields.Char('开机时长')
online_time = fields.Char('开机时长', reaonly='True')
offline_time = fields.Char('关机时长')
offline_nums = fields.Integer('关机次数')
offline_time = fields.Char('关机时长', reaonly='True')
offline_nums = fields.Integer('关机次数', reaonly='True')
# 待机时长
idle_time = fields.Char('待机时长')
idle_time = fields.Char('待机时长', reaonly='True')
# 待机率
idle_rate = fields.Char('待机率')
idle_rate = fields.Char('待机率', reaonly='True')
work_time = fields.Char('加工时长')
work_rate = fields.Char('可用率')
fault_time = fields.Char('故障时长')
fault_rate = fields.Char('故障率')
fault_nums = fields.Integer('故障次数')
work_time = fields.Char('加工时长', reaonly='True')
work_rate = fields.Char('可用率', reaonly='True')
fault_time = fields.Char('故障时长', reaonly='True')
fault_rate = fields.Char('故障率', reaonly='True')
fault_nums = fields.Integer('故障次数', reaonly='True')
detail_ids = fields.One2many('maintenance.equipment.oee.log.detail', 'log_id', string='日志详情')
@@ -81,12 +84,15 @@ class SfMaintenanceEquipmentOEELog(models.Model):
class SfMaintenanceEquipmentOEELogDetail(models.Model):
_name = 'maintenance.equipment.oee.log.detail'
_description = '设备运行日志详情'
_order = 'time desc'
sequence = fields.Integer('序号')
# sequence = fields.Integer('序号', related='id')
time = fields.Datetime('时间')
state = fields.Selection([("加工", "加工"), ("关机", "关机"), ("待机", "待机"), ("故障", "故障"),
("检修", "检修"), ("保养", "保养")], default="", string="事件/状态")
production_id = fields.Many2one('mrp.production', '加工工单')
production_name = fields.Char('加工工单')
log_id = fields.Many2one('maintenance.equipment.oee.logs', '日志')
# equipment_code = fields.Char('设备编码', related='log_id.equipment_code')
equipment_code = fields.Char('设备编码', readonly='True')

View File

@@ -159,6 +159,8 @@
<field name="equipment_id" domain="[('name','ilike','加工中心')]"/>
<field name="type_id"/>
<field name="state"/>
<field name="equipment_code"/>
<field name="function_type"/>
</group>
</group>
<group>
@@ -202,10 +204,10 @@
<!-- <field name="detail_ids" domain="[('time','&lt;',(datetime.datetime.now() - datetime.timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S'))]"> -->
<field name="detail_ids" domain="[('state','ilike','加工')]">
<tree>
<field name="sequence"/>
<!-- <field name="sequence"/> -->
<field name="time"/>
<field name="state"/>
<field name="production_id"/>
<field name="production_name"/>
</tree>
<!-- <form> -->
<!-- <field name="sequence"/> -->
@@ -219,10 +221,10 @@
<page string="历史日志详情">
<field name="detail_ids">
<tree>
<field name="sequence"/>
<!-- <field name="sequence"/> -->
<field name="time"/>
<field name="state"/>
<field name="production_id"/>
<field name="production_name"/>
</tree>
<!-- <form> -->
<!-- <field name="sequence"/> -->
@@ -263,10 +265,10 @@
<field name="model">maintenance.equipment.oee.log.detail</field>
<field name="arch" type="xml">
<tree>
<field name="sequence"/>
<!-- <field name="sequence"/> -->
<field name="time"/>
<field name="state"/>
<field name="production_id"/>
<field name="production_name"/>
</tree>
</field>
</record>
@@ -280,10 +282,10 @@
<group>
<group>
<field name="state"/>
<field name="production_id"/>
<!-- <field name="production_id"/> -->
</group>
<group>
<field name="sequence"/>
<!-- <field name="sequence"/> -->
<field name="time"/>
</group>
</group>

View File

@@ -15,12 +15,14 @@
'data/stock_data.xml',
'data/empty_racks_data.xml',
'data/panel_data.xml',
'data/agv_scheduling_data.xml',
'security/group_security.xml',
'security/ir.model.access.csv',
'wizard/workpiece_delivery_views.xml',
'wizard/rework_wizard_views.xml',
'wizard/production_wizard_views.xml',
'views/mrp_views_menus.xml',
'views/agv_scheduling_views.xml',
'views/stock_lot_views.xml',
'views/mrp_production_addional_change.xml',
'views/mrp_routing_workcenter_view.xml',
@@ -30,7 +32,7 @@
'views/model_type_view.xml',
'views/agv_setting_views.xml',
'views/sf_maintenance_equipment.xml',
'views/res_config_settings_views.xml',
],
'assets': {
@@ -40,7 +42,9 @@
'web.assets_backend': [
'sf_manufacturing/static/src/xml/kanban_change.xml',
'sf_manufacturing/static/src/js/kanban_change.js',
'sf_manufacturing/static/src/scss/kanban_change.scss'
'sf_manufacturing/static/src/scss/kanban_change.scss',
'sf_manufacturing/static/src/xml/button_show_on_tree.xml',
'sf_manufacturing/static/src/js/workpiece_delivery_wizard_confirm.js',
]
},

View File

@@ -2,6 +2,8 @@
import logging
import json
from datetime import datetime
from odoo.addons.sf_manufacturing.models.agv_scheduling import RepeatTaskException
from odoo import http
from odoo.http import request
@@ -145,7 +147,7 @@ class Manufacturing_Connect(http.Controller):
logging.info('get_qcCheck error:%s' % e)
return json.JSONEncoder().encode(res)
@http.route('/AutoDeviceApi/FeedBackStart', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
@http.route('/AutoDeviceApi/FeedBackStart', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
def button_Work_START(self, **kw):
"""
@@ -193,7 +195,7 @@ class Manufacturing_Connect(http.Controller):
logging.info('button_Work_START error:%s' % e)
return json.JSONEncoder().encode(res)
@http.route('/AutoDeviceApi/FeedBackEnd', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
@http.route('/AutoDeviceApi/FeedBackEnd', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
def button_Work_End(self, **kw):
"""
@@ -244,7 +246,7 @@ class Manufacturing_Connect(http.Controller):
logging.info('button_Work_End error:%s' % e)
return json.JSONEncoder().encode(res)
@http.route('/AutoDeviceApi/PartQualityInspect', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
@http.route('/AutoDeviceApi/PartQualityInspect', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
def PartQualityInspect(self, **kw):
"""
@@ -290,7 +292,7 @@ class Manufacturing_Connect(http.Controller):
logging.info('PartQualityInspect error:%s' % e)
return json.JSONEncoder().encode(res)
@http.route('/AutoDeviceApi/CMMProgDolod', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
@http.route('/AutoDeviceApi/CMMProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
def CMMProgDolod(self, **kw):
"""
@@ -330,7 +332,7 @@ class Manufacturing_Connect(http.Controller):
logging.info('CMMProgDolod error:%s' % e)
return json.JSONEncoder().encode(res)
@http.route('/AutoDeviceApi/NCProgDolod', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
@http.route('/AutoDeviceApi/NCProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
def NCProgDolod(self, **kw):
"""
@@ -386,7 +388,7 @@ class Manufacturing_Connect(http.Controller):
ret = json.loads(datas)
request.env['center_control.interface.log'].sudo().create(
{'content': ret, 'name': 'AutoDeviceApi/LocationChange'})
logging.info('LocationChange_ret===========:%s' % ret)
logging.info('库位变更LocationChange_ret:%s' % ret)
RfidCode = ret['RfidCode']
ChangeType = ret['ChangeType']
OldDeciveId = ret['OldDeciveId']
@@ -396,34 +398,80 @@ class Manufacturing_Connect(http.Controller):
OldDeciveStart = ret['OldDeciveStart']
OldDeciveEnd = ret['OldDeciveEnd']
temp_val_sn_id = None
old_localtion = None
# if ChangeType == 'Part' or ChangeType == 'Tool':
stock_lot_obj = request.env['stock.lot'].sudo().search(
[('rfid', '=', RfidCode)], limit=1)
logging.info('stock_lot_obj===========:%s' % stock_lot_obj)
if not stock_lot_obj:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': '未根据RfidCode找到该产品'}
return json.JSONEncoder().encode(res)
if OldPosition:
old_localtion = request.env['sf.shelf.location'].sudo().search(
[('barcode', '=', OldPosition)], limit=1)
logging.info('old_localtion===========:%s' % old_localtion)
new_localtion = request.env['sf.shelf.location'].sudo().search(
[('barcode', '=', NewPosition)], limit=1)
logging.info('new_localtion===========:%s' % new_localtion)
if not new_localtion:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': '没有该目标位置'}
return json.JSONEncoder().encode(res)
if old_localtion:
temp_val_sn_id = old_localtion.product_sn_id
logging.info('temp_val_sn_id===========:%s' % temp_val_sn_id)
old_localtion.product_sn_id = None
new_localtion.product_sn_id = temp_val_sn_id
logging.info('====1======')
else:
new_localtion.product_sn_id = stock_lot_obj.id
logging.info('=====2======')
if ChangeType == 'Part':
temp_val_sn_id = None
old_localtion = None
# if ChangeType == 'Part' or ChangeType == 'Tool':
stock_lot_obj = request.env['stock.lot'].sudo().search(
[('rfid', '=', RfidCode)], limit=1)
logging.info('stock_lot_obj===========:%s' % stock_lot_obj)
if not stock_lot_obj:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': '未根据RfidCode找到该产品'}
return json.JSONEncoder().encode(res)
if OldPosition:
old_localtion = request.env['sf.shelf.location'].sudo().search(
[('barcode', '=', OldPosition)], limit=1)
logging.info('old_localtion===========:%s' % old_localtion)
new_localtion = request.env['sf.shelf.location'].sudo().search(
[('barcode', '=', NewPosition)], limit=1)
logging.info('new_localtion===========:%s' % new_localtion)
if not new_localtion:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': '没有该目标位置'}
return json.JSONEncoder().encode(res)
if old_localtion:
temp_val_sn_id = old_localtion.product_sn_id
logging.info('temp_val_sn_id===========:%s' % temp_val_sn_id)
old_localtion.product_sn_id = None
new_localtion.product_sn_id = temp_val_sn_id
logging.info('====1======')
else:
new_localtion.product_sn_id = stock_lot_obj.id
logging.info('=====2======')
elif ChangeType == 'Tool':
# 对功能刀具库位变更信息进行更改
def write_tool(DeciveId):
if 'Tool' in DeciveId:
shelfinfo = list(filter(lambda x: x.get('DeviceId') == DeciveId,
request.env['sf.shelf.location'].sudo().get_sf_shelf_location_info(
DeciveId)))
total_data = request.env['sf.shelf.location.datasync'].sudo().get_total_data()
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:
equipment_id = request.env['maintenance.equipment'].sudo().search([('name', '=', DeciveId)])
if equipment_id:
equipment_id.sudo().register_equipment_tool()
else:
res_1 = {'Succeed': False, 'ErrorCode': 202, 'Error': f'设备【{DeciveId}】不存在'}
return json.JSONEncoder().encode(res_1)
if OldDeciveId:
write_tool(OldDeciveId)
elif NewDeciveId:
write_tool(NewDeciveId)
except Exception as e:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
logging.info('LocationChange error:%s' % e)
@@ -448,13 +496,16 @@ class Manufacturing_Connect(http.Controller):
if 'DeviceId' in ret:
logging.info('DeviceId:%s' % ret['DeviceId'])
if 'IsComplete' in ret:
rfid_codes = []
workorder_ids = []
if ret['IsComplete'] is True or ret['IsComplete'] is False:
for i in range(1, 5):
logging.info('F-RfidCode:%s' % i)
if f'RfidCode{i}' in ret:
rfid_code = ret[f'RfidCode{i}']
logging.info('RfidCode:%s' % rfid_code)
if rfid_code is not None:
if rfid_code is not None and rfid_code != '':
rfid_codes.append(rfid_code)
domain = [
('rfid_code', '=', rfid_code),
('routing_type', '=', 'CNC加工'), ('state', '!=', 'rework')
@@ -462,6 +513,7 @@ class Manufacturing_Connect(http.Controller):
workorder = request.env['mrp.workorder'].sudo().search(domain, order='id asc')
if workorder:
for order in workorder:
workorder_ids.append(order.id)
if order.production_line_state == '待上产线':
logging.info(
'工单产线状态:%s' % order.production_line_state)
@@ -470,23 +522,30 @@ class Manufacturing_Connect(http.Controller):
('processing_panel', '=', order.processing_panel)])
if panel_workorder:
panel_workorder.write({'production_line_state': '已上产线'})
workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
[
('rfid_code', '=', rfid_code), ('type', '=', '上产线'),
('production_id', '=', order.production_id.id),
('workorder_id', '=', order.id),
('workorder_state', '=', 'done')])
if workpiece_delivery.status == '待下发':
workpiece_delivery.write({'is_manual_work': True})
# workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
# [
# ('rfid_code', '=', rfid_code), ('type', '=', '上产线'),
# ('production_id', '=', order.production_id.id),
# ('workorder_id', '=', order.id),
# ('workorder_state', '=', 'done')])
# if workpiece_delivery.status == '待下发':
# workpiece_delivery.write({'is_manual_work': True})
# 下发
else:
res = {'Succeed': False, 'ErrorCode': 204,
'Error': 'DeviceId为%s没有对应的已配送工件数据' % ret['DeviceId']}
if ret['IsComplete'] is True:
# 向AGV任务调度下发运送空料架任务
workorders = request.env['mrp.workorder'].browse(workorder_ids)
request.env['sf.agv.scheduling'].add_scheduling(ret['DeviceId'], '运送空料架', workorders)
else:
res = {'Succeed': False, 'ErrorCode': 203, 'Error': '未传IsComplete字段'}
else:
res = {'Succeed': False, 'ErrorCode': 201, 'Error': '未传DeviceId字段'}
except RepeatTaskException as e:
logging.info('AGVToProduct error:%s' % e)
except Exception as e:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
res = {'Succeed': False, 'ErrorCode': 202, 'Error': str(e)}
logging.info('AGVToProduct error:%s' % e)
return json.JSONEncoder().encode(res)
@@ -509,7 +568,8 @@ class Manufacturing_Connect(http.Controller):
logging.info('ret:%s' % ret)
if 'DeviceId' in ret:
logging.info('DeviceId:%s' % ret['DeviceId'])
delivery_Arr = []
# delivery_Arr = []
workorder_ids = []
if 'IsComplete' in ret:
if ret['IsComplete'] is True or ret['IsComplete'] is False:
for i in range(1, 5):
@@ -517,7 +577,7 @@ class Manufacturing_Connect(http.Controller):
if f'RfidCode{i}' in ret:
rfid_code = ret[f'RfidCode{i}']
logging.info('RfidCode:%s' % rfid_code)
if rfid_code is not None:
if rfid_code is not None and rfid_code != '':
domain = [
('rfid_code', '=', rfid_code),
('routing_type', '=', 'CNC加工'), ('state', '!=', 'rework')
@@ -525,6 +585,7 @@ class Manufacturing_Connect(http.Controller):
workorder = request.env['mrp.workorder'].sudo().search(domain, order='id asc')
if workorder:
for order in workorder:
workorder_ids.append(order.id)
if order.production_line_state == '已上产线':
logging.info(
'工单产线状态:%s' % order.production_line_state)
@@ -534,35 +595,41 @@ class Manufacturing_Connect(http.Controller):
if panel_workorder:
panel_workorder.write({'production_line_state': '已下产线'})
workorder.write({'state': 'to be detected'})
workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
[
('rfid_code', '=', rfid_code), ('type', '=', '下产线'),
('production_id', '=', order.production_id.id),
('workorder_id', '=', order.id),
('workorder_state', '=', 'done')])
if workpiece_delivery:
delivery_Arr.append(workpiece_delivery.id)
# workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
# [
# ('rfid_code', '=', rfid_code), ('type', '=', '下产线'),
# ('production_id', '=', order.production_id.id),
# ('workorder_id', '=', order.id),
# ('workorder_state', '=', 'done')])
# if workpiece_delivery:
# delivery_Arr.append(workpiece_delivery.id)
else:
res = {'Succeed': False, 'ErrorCode': 204,
'Error': 'DeviceId为%s没有对应的已配送工件数据' % ret['DeviceId']}
if delivery_Arr:
logging.info('delivery_Arr:%s' % delivery_Arr)
delivery_workpiece = request.env['sf.workpiece.delivery'].sudo().search(
[('id', 'in', delivery_Arr)])
if delivery_workpiece:
logging.info('开始向agv下发下产线任务')
agv_site = request.env['sf.agv.site'].sudo().search([])
if agv_site:
has_site = agv_site.update_site_state()
if has_site is True:
is_free = delivery_workpiece._check_avgsite_state()
if is_free is True:
delivery_workpiece._delivery_avg()
logging.info('agv下发下产线任务下发完成')
# if delivery_Arr:
# logging.info('delivery_Arr:%s' % delivery_Arr)
# delivery_workpiece = request.env['sf.workpiece.delivery'].sudo().search(
# [('id', 'in', delivery_Arr)])
# if delivery_workpiece:
# logging.info('开始向agv下发下产线任务')
# agv_site = request.env['sf.agv.site'].sudo().search([])
# if agv_site:
# has_site = agv_site.update_site_state()
# if has_site is True:
# is_free = delivery_workpiece._check_avgsite_state()
# if is_free is True:
# delivery_workpiece._delivery_avg()
# logging.info('agv下发下产线任务下发完成')
if ret['IsComplete'] is True:
# 向AGV任务调度下发下产线任务
workorders = request.env['mrp.workorder'].browse(workorder_ids)
request.env['sf.agv.scheduling'].add_scheduling(ret['DeviceId'], '下产线', workorders)
else:
res = {'Succeed': False, 'ErrorCode': 203, 'Error': '未传IsComplete字段'}
except RepeatTaskException as e:
logging.info('AGVToProduct error:%s' % e)
except Exception as e:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
res = {'Succeed': False, 'ErrorCode': 202, 'Error': str(e)}
logging.info('AGVDownProduct error:%s' % e)
return json.JSONEncoder().encode(res)
@@ -600,3 +667,32 @@ class Manufacturing_Connect(http.Controller):
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}
logging.info('AGVDownProduct error:%s' % e)
return json.JSONEncoder().encode(res)
@http.route('/AutoDeviceApi/AgvStationState', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*")
def AGVStationState(self, **kw):
"""
中控推送接驳站状态
:param kw:
:return:
"""
logging.info('AGVStationState:%s' % kw)
try:
res = {'Succeed': True}
datas = request.httprequest.data
ret = json.loads(datas)
request.env['center_control.interface.log'].sudo().create(
{'content': ret, 'name': 'AutoDeviceApi/AGVStationState'})
logging.info('ret:%s' % ret)
ret = ret['param']
params = {}
for i in range(len(ret)):
if 'DeviceId' in ret[i] and 'AtHome' in ret[i]:
logging.info('DeviceId:%s, AtHome:%s' % (ret[i]['DeviceId'], ret[i]['AtHome']))
params[ret[i]['DeviceId']] = '占用' if ret[i]['AtHome'] else '空闲'
if params:
request.env['sf.agv.site'].update_site_state(params)
except Exception as e:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': str(e)}
logging.info('AGVDownProduct error:%s' % e)
return json.JSONEncoder().encode(res)

View File

@@ -8,7 +8,7 @@ from odoo.http import request
class Workpiece(http.Controller):
@http.route('/agvApi/backfeed', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
@http.route('/agvApi/backfeed', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*")
def backfeed(self, **kw):
"""
@@ -25,15 +25,14 @@ class Workpiece(http.Controller):
if 'reqCode' in ret:
if 'method' in ret:
if ret['method'] == 'end':
req_codes = ret['reqCode'].split(',')
for req_code in req_codes:
workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
[('name', '=', req_code.strip()), ('task_completion_time', '=', False)])
if workpiece_delivery:
workpiece_delivery.write({'status': '已配送', 'task_completion_time': datetime.now()})
else:
res = {'Succeed': False, 'ErrorCode': 203,
'Error': '该reqCode暂未查到对应的工件配送记录'}
# 找到对应的AGV调度任务
agv_scheduling = request.env['sf.agv.scheduling'].sudo().search(
[('name', '=', ret['reqCode']), ('state', '=', '配送中')])
if agv_scheduling:
agv_scheduling.finish_scheduling()
else:
res = {'Succeed': False, 'ErrorCode': 203,
'Error': '该reqCode暂未查到对应的AGV任务记录'}
else:
res = {'Succeed': False, 'ErrorCode': 204, 'Error': '未传method字段'}
else:

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<record id="sequence_agv_scheduling" model="ir.sequence">
<field name="name">AGV调度</field>
<field name="code">sf.agv.scheduling</field>
<field name="prefix">B%(year)s%(month)s%(day)s</field>
<field name="padding">4</field>
<field name="number_next">1</field>
<field name="implementation">standard</field>
<field name="use_date_range">True</field>
<field name="date_range_period">day</field>
<field name="company_id" eval="False"/>
</record>
</data>
</odoo>

View File

@@ -9,3 +9,5 @@ from . import stock
from . import res_user
from . import production_line_base
from . import agv_setting
from . import agv_scheduling
from . import res_config_setting

View File

@@ -0,0 +1,267 @@
import requests
from odoo import models, fields, api, _
from odoo.exceptions import UserError
import logging
_logger = logging.getLogger(__name__)
class RepeatTaskException(UserError):
pass
class AgvScheduling(models.Model):
_name = 'sf.agv.scheduling'
_description = 'agv调度'
_order = 'id desc'
name = fields.Char('任务单号', index=True, copy=False)
def _get_agv_route_type_selection(self):
return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection']
agv_route_type = fields.Selection(selection=_get_agv_route_type_selection, string='任务类型', required=True)
agv_route_id = fields.Many2one('sf.agv.task.route', '任务路线')
start_site_id = fields.Many2one('sf.agv.site', '起点接驳站', required=True)
end_site_id = fields.Many2one('sf.agv.site', '终点接驳站', tracking=True)
site_state = fields.Selection([
('占用', '占用'),
('空闲', '空闲')], string='终点接驳站状态', default='占用')
state = fields.Selection([
('待下发', '待下发'),
('配送中', '配送中'),
('已配送', '已配送'),
('已取消', '已取消')], string='状态', default='待下发', tracking=True)
workorder_ids = fields.Many2many('mrp.workorder', 'sf_agv_scheduling_mrp_workorder_ref', string='关联工单')
task_create_time = fields.Datetime('任务创建时间')
task_delivery_time = fields.Datetime('任务下发时间')
task_completion_time = fields.Datetime('任务完成时间')
task_duration = fields.Char('任务时长', compute='_compute_task_duration')
@api.depends('agv_route_type')
def _compute_delivery_workpieces(self):
for record in self:
if record.agv_route_type == '运送空料架':
record.delivery_workpieces = '/'
else:
record.delivery_workpieces = ''.join(record.workorder_ids.mapped('production_id.name'))
delivery_workpieces = fields.Char('配送工件', compute=_compute_delivery_workpieces)
@api.model
def web_search_read(self, domain=None, fields=None, offset=0, limit=None, order=None, count_limit=None):
domain = domain or []
new_domain = []
for index, item in enumerate(domain):
if isinstance(item, list):
if item[0] == 'delivery_workpieces':
new_domain.append('&')
new_domain.append(['workorder_ids.production_id.name', item[1], item[2]])
new_domain.append(['agv_route_type', '!=', '运送空料架'])
continue
new_domain.append(item)
return super(AgvScheduling, self).web_search_read(new_domain, fields, limit=limit, offset=offset)
@api.depends('task_completion_time', 'task_delivery_time')
def _compute_task_duration(self):
for rec in self:
if rec.task_completion_time and rec.task_delivery_time:
rec.task_duration = str(rec.task_completion_time - rec.task_delivery_time)
else:
rec.task_duration = ''
@api.model_create_multi
def create(self, vals_list):
# We generate a standard reference
for vals in vals_list:
vals['name'] = self.env['ir.sequence'].next_by_code('sf.agv.scheduling') or _('New')
return super().create(vals_list)
def add_scheduling(self, agv_start_site_name, agv_route_type, workorders):
""" add_scheduling(agv_start_site_id, agv_route_type, workorders) -> agv_scheduling
新增AGV调度
params:
agv_start_site_name: AGV起点接驳站名称
agv_route_type: AGV任务类型
workorders: 工单
"""
_logger.info('创建AGV调度任务\r\n起点为【%s】,任务类型为【%s】,工单为【%s' % (agv_start_site_name, agv_route_type, workorders))
if not workorders:
raise UserError(_('工单不能为空'))
agv_start_site = self.env['sf.agv.site'].sudo().search([('name', '=', agv_start_site_name)], limit=1)
if not agv_start_site:
raise UserError(_('不存在名称为【%s】的接驳站,请先创建!' % agv_start_site_name))
# 如果存在相同任务类型工单的AGV调度任务则提示错误
agv_scheduling = self.sudo().search([
('workorder_ids', 'in', workorders.ids),
('agv_route_type', '=', agv_route_type),
('state', 'in', ['待下发', '配送中'])
], limit=1)
if agv_scheduling:
# 计算agv_scheduling.workorder_ids与workorders的交集
repetitive_workorders = agv_scheduling.workorder_ids & workorders
raise RepeatTaskException(
'制造订单号【%s】已存在于【%s】AGV调度任务请勿重复下发' %
(','.join(repetitive_workorders.mapped('production_id.name')), agv_scheduling.name)
)
vals = {
'start_site_id': agv_start_site.id,
'agv_route_type': agv_route_type,
'workorder_ids': workorders.ids,
# 'workpiece_delivery_ids': deliveries.mapped('id') if deliveries else [],
'task_create_time': fields.Datetime.now()
}
# 如果只有唯一任务路线,则自动赋予终点接驳站跟任务名称
agv_routes = self.env['sf.agv.task.route'].sudo().search([
('route_type', '=', agv_route_type),
('start_site_id', '=', agv_start_site.id)
])
if not agv_routes:
raise UserError(_('不存在起点为【%s】的【%s】任务路线,请先创建!' % (agv_start_site_name, agv_route_type)))
idle_route = None
if len(agv_routes) == 1:
idle_route = agv_routes[0]
vals.update({'end_site_id': idle_route.end_site_id.id, 'agv_route_id': idle_route.id})
else:
# 判断终点接驳站是否为空闲
idle_routes = agv_routes.filtered(lambda r: r.end_site_id.state == '空闲')
if idle_routes:
# 将空闲的路线按照终点接驳站名称排序
idle_routes = sorted(idle_routes, key=lambda r: r.end_site_id.name)
idle_route = idle_routes[0]
vals.update({'end_site_id': idle_route.end_site_id.id, 'agv_route_id': idle_route.id})
try:
scheduling = self.env['sf.agv.scheduling'].sudo().create(vals)
# 触发空闲接驳站状态更新,触发新任务下发
if idle_route and idle_route.end_site_id.state == '空闲':
scheduling.dispatch_scheduling(idle_route)
except Exception as e:
_logger.error('添加AGV调度任务失败: %s', e)
raise UserError(_('添加AGV调度任务失败: %s', e))
return scheduling
def on_site_state_change(self, agv_site_id, agv_site_state):
"""
响应AGV接驳站站点状态变化
params:
agv_site_id: 接驳站ID
agv_site_state: 站点状态('空闲', '占用'
"""
if agv_site_state == '空闲':
# 查询终点接驳站为agv_site_id的AGV路线
task_routes = self.env['sf.agv.task.route'].sudo().search([('end_site_id', '=', agv_site_id)])
agv_scheduling = self.env['sf.agv.scheduling'].sudo().search(
[('state', '=', '待下发'), ('agv_route_type', 'in', task_routes.mapped('route_type'))],
order='id asc',
limit=1
)
task_route = task_routes.filtered(
lambda r: r.start_site_id == agv_scheduling.start_site_id and r.start_site_id == agv_scheduling.start_site_id
)
if task_route:
# 下发AGV调度任务并修改接驳站状态为占用
agv_scheduling.dispatch_scheduling(task_route)
def _delivery_avg(self):
config = self.env['res.config.settings'].get_values()
position_code_arr = [{
'positionCode': self.start_site_id.name,
'code': '00'
}, {
'positionCode': self.end_site_id.name,
'code': '00'
}]
res = {'reqCode': self.name, 'reqTime': '', 'clientCode': '', 'tokenCode': '',
'taskTyp': 'F01', 'ctnrTyp': '', 'ctnrCode': '', 'wbCode': config['wbcode'],
'positionCodePath': position_code_arr,
'podCode': '',
'podDir': '', 'materialLot': '', 'priority': '', 'taskCode': '', 'agvCode': '', 'materialLot': '',
'data': ''}
try:
logging.info('AGV请求路径:%s' % config['agv_rcs_url'])
logging.info('AGV-json:%s' % res)
headers = {'Content-Type': 'application/json'}
ret = requests.post((config['agv_rcs_url']), json=res, headers=headers)
ret = ret.json()
logging.info('config-ret:%s' % ret)
if ret['code'] == 0:
return True
else:
raise UserError(ret['message'])
except Exception as e:
logging.info('config-e:%s' % e)
raise UserError("工件配送请求agv失败:%s" % e)
def button_cancel(self):
# 弹出二次确认窗口后执行
for rec in self:
if rec.state != '待下发':
raise UserError('只有待下发状态的AGV调度任务才能取消')
rec.state = '已取消'
def finish_scheduling(self):
"""
完成调度任务
"""
for rec in self:
if rec.state != '配送中':
return False
_logger.info('AGV任务调度完成任务%s' % rec)
rec.state = '已配送'
rec.task_completion_time = fields.Datetime.now()
def dispatch_scheduling(self, agv_task_route):
"""
下发调度任务
params:
agv_route sf.agv.task.route对象
"""
for rec in self:
if rec.state != '待下发':
return False
_logger.info('AGV任务调度下发调度任务路线为%s' % agv_task_route)
rec.state = '配送中'
rec.task_delivery_time = fields.Datetime.now()
rec.site_state = '空闲'
rec.end_site_id = agv_task_route.end_site_id.id
rec.agv_route_id = agv_task_route.id
is_agv_task_dispatch = self.env['ir.config_parameter'].sudo().get_param('is_agv_task_dispatch')
if is_agv_task_dispatch:
rec._delivery_avg()
# 更新接驳站状态
rec.env['sf.agv.site'].update_site_state({rec.end_site_id.name: '占用'}, False)
def write(self, vals):
if vals.get('state', False):
if vals['state'] == '已取消':
self.env['sf.workpiece.delivery'].search([('agv_scheduling_id', '=', self.id)]).write({'status': '待下发'})
elif vals['state'] == '已配送':
self.env['sf.workpiece.delivery'].search([('agv_scheduling_id', '=', self.id)]).write({
'status': '已配送',
'feeder_station_destination_id': self.end_site_id.id,
'route_id': self.agv_route_id.id,
'task_completion_time': fields.Datetime.now()
})
elif vals['state'] == '配送中':
self.env['sf.workpiece.delivery'].search([('agv_scheduling_id', '=', self.id)]).write({
'feeder_station_destination_id': self.end_site_id.id,
'route_id': self.agv_route_id.id,
'task_delivery_time': fields.Datetime.now()
})
return super().write(vals)
class ResMrpWorkOrder(models.Model):
_inherit = 'mrp.workorder'
agv_scheduling_ids = fields.Many2many(
'sf.agv.scheduling',
'sf_agv_scheduling_mrp_workorder_ref',
string='AGV调度',
domain=[('state', '!=', '已取消')])

View File

@@ -5,50 +5,77 @@ import time
from odoo import fields, models, api
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
class AgvSetting(models.Model):
_name = 'sf.agv.site'
_description = 'agv站点'
name = fields.Char('位置编号')
owning_region = fields.Char('所属区域')
# owning_region = fields.Char('所属区域')
state = fields.Selection([
('占用', '占用'),
('空闲', '空闲')], string='状态')
divide_the_work = fields.Char('主要分工')
active = fields.Boolean('有效', default=True)
workcenter_id = fields.Many2one(string='所属区域', comodel_name='mrp.workcenter', tracking=True,
domain=[('is_agv_scheduling', '=', True)])
def update_site_state(self):
# 调取中控的接驳站接口并修改对应站点的状态
config = self.env['res.config.settings'].get_values()
# token = sf_sync_config['token'Ba F2CF5DCC-1A00-4234-9E95-65603F70CC8A]
headers = {'Authorization': config['center_control_Authorization']}
center_control_url = config['center_control_url'] + "/AutoDeviceApi/GetAgvStationState?date="
timestamp = int(time.time())
center_control_url += str(timestamp)
logging.info('工件配送-请求中控地址:%s' % center_control_url)
try:
center_control_r = requests.get(center_control_url, headers=headers, timeout=10) # 设置超时为60秒
ret = center_control_r.json()
logging.info('工件配送-请求中控站点信息:%s' % ret)
self.env['center_control.interface.log'].sudo().create(
{'content': ret, 'name': 'AutoDeviceApi/GetAgvStationState?date=%s' % str(timestamp)})
if ret['Succeed'] is True:
datas = ret['Datas']
for item in self:
for da in datas:
if da['DeviceId'] == item.name:
if da['AtHome'] is True:
item.state = '占用'
else:
item.state = '空闲'
return True
except requests.exceptions.Timeout:
logging.error('工件配送-请求中控接口超时')
return False
except requests.exceptions.RequestException as e:
logging.error('工件配送-请求中控接口错误: %s', e)
return False
# name必须唯一
_sql_constraints = [
('name_uniq', 'unique (name)', '站点编号必须唯一!'),
]
# def update_site_state(self):
# # 调取中控的接驳站接口并修改对应站点的状态
# config = self.env['res.config.settings'].get_values()
# # token = sf_sync_config['token'Ba F2CF5DCC-1A00-4234-9E95-65603F70CC8A]
# headers = {'Authorization': config['center_control_Authorization']}
# center_control_url = config['center_control_url'] + "/AutoDeviceApi/GetAgvStationState?date="
# timestamp = int(time.time())
# center_control_url += str(timestamp)
# logging.info('工件配送-请求中控地址:%s' % center_control_url)
# try:
# center_control_r = requests.get(center_control_url, headers=headers, timeout=10) # 设置超时为60秒
# ret = center_control_r.json()
# logging.info('工件配送-请求中控站点信息:%s' % ret)
# self.env['center_control.interface.log'].sudo().create(
# {'content': ret, 'name': 'AutoDeviceApi/GetAgvStationState?date=%s' % str(timestamp)})
# if ret['Succeed'] is True:
# datas = ret['Datas']
# for item in self:
# for da in datas:
# if da['DeviceId'] == item.name:
# if da['AtHome'] is True:
# item.state = '占用'
# else:
# item.state = '空闲'
# return True
# except requests.exceptions.Timeout:
# logging.error('工件配送-请求中控接口超时')
# return False
# except requests.exceptions.RequestException as e:
# logging.error('工件配送-请求中控接口错误: %s', e)
# return False
def update_site_state(self, agv_site_state_arr, notify=True):
"""
更新接驳站状态
params:
agv_site_state_arr: {'A01': '空闲', 'B01': '占用'}
notify: 是否通知调度(非中控发起的状态改变不触发调度任务)
"""
if isinstance(agv_site_state_arr, dict):
for agv_site_name, is_occupy in agv_site_state_arr.items():
agv_site = self.env['sf.agv.site'].sudo().search([('name', '=', agv_site_name)])
if agv_site:
agv_site.state = is_occupy
if notify:
self.env['sf.agv.scheduling'].on_site_state_change(agv_site.id, agv_site.state)
else:
_logger.error("更新失败:接驳站站点错误!%s" % agv_site_name)
raise UserError("更新失败:接驳站站点错误!")
class AgvTaskRoute(models.Model):
@@ -71,6 +98,17 @@ class AgvTaskRoute(models.Model):
if self.end_site_id == self.start_site_id:
raise UserError("您选择的终点接驳站与起点接驳站重复,请重新选择")
workcenter_id = fields.Many2one(string='所属区域', comodel_name='mrp.workcenter', domain=[('is_agv_scheduling', '=', True)],
compute="_compute_region")
@api.depends('end_site_id')
def _compute_region(self):
for record in self:
if record.end_site_id:
record.workcenter_id = record.end_site_id.workcenter_id
else:
record.workcenter_id = None
class Center_controlInterfaceLog(models.Model):
_name = 'center_control.interface.log'

View File

@@ -1,12 +1,14 @@
# -*- coding: utf-8 -*-
import base64
import datetime
import logging
import json
import os
import re
import requests
from itertools import groupby
from odoo import api, fields, models, _
from collections import defaultdict, namedtuple
from odoo import api, fields, models, SUPERUSER_ID, _
from odoo.exceptions import UserError, ValidationError
from odoo.addons.sf_base.commons.common import Common
from odoo.tools import float_compare, float_round, float_is_zero, format_datetime
@@ -77,9 +79,10 @@ class MrpProduction(models.Model):
('pending_cam', '待加工'),
('progress', '加工中'),
('rework', '返工'),
('scrap', '报废'),
('to_close', 'To Close'),
('done', 'Done'),
('cancel', '报废')], string='State',
('cancel', '已取消')], string='State',
compute='_compute_state', copy=False, index=True, readonly=True,
store=True, tracking=True,
help=" * Draft: The MO is not confirmed yet.\n"
@@ -120,8 +123,39 @@ class MrpProduction(models.Model):
# 上传零件图纸
part_drawing = fields.Binary('零件图纸')
manual_quotation = fields.Boolean('人工编程', default=False, readonly=True)
@api.depends('product_id.manual_quotation')
def _compute_manual_quotation(self):
for item in self:
item.manual_quotation = item.product_id.manual_quotation
manual_quotation = fields.Boolean('人工编程', default=False, compute=_compute_manual_quotation, store=True)
is_scrap = fields.Boolean('是否报废', default=False)
is_remanufacture = fields.Boolean('是否重新制造', default=False)
remanufacture_count = fields.Integer("重新制造订单数量", compute='_compute_remanufacture_production_ids')
remanufacture_production_id = fields.Many2one('mrp.production', string='')
@api.depends('remanufacture_production_id')
def _compute_remanufacture_production_ids(self):
for production in self:
if production.remanufacture_production_id:
remanufacture_production = self.env['mrp.production'].search(
[('id', '=', production.remanufacture_production_id.id)])
if remanufacture_production:
production.remanufacture_count = len(remanufacture_production)
else:
production.remanufacture_count = 0
def action_view_remanufacture_productions(self):
self.ensure_one()
mrp_production = self.env['mrp.production'].search(
[('id', '=', self.remanufacture_production_id.id)])
action = {
'res_model': 'mrp.production',
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_id': mrp_production.id,
}
return action
@api.depends(
'move_raw_ids.state', 'move_raw_ids.quantity_done', 'move_finished_ids.state', 'tool_state',
@@ -166,7 +200,7 @@ class MrpProduction(models.Model):
production.state = 'pending_cam'
if production.state == 'progress':
if all(wo_state not in ('progress', 'done', 'rework') for wo_state in
if all(wo_state not in ('progress', 'done', 'rework', 'scrap') for wo_state in
production.workorder_ids.mapped('state')):
production.state = 'pending_cam'
if production.is_rework is True:
@@ -184,6 +218,11 @@ class MrpProduction(models.Model):
for wo in
production.workorder_ids):
production.state = 'rework'
if any(wo.test_results == '报废' and wo.state == 'done' for wo in production.workorder_ids):
production.state = 'scrap'
if any(dr.test_results == '报废' and dr.handle_result == '已处理' for dr in
production.detection_result_ids):
production.state = 'cancel'
# 如果制造订单的功能刀具为【无效刀】则制造订单状态改为返工
if production.tool_state == '2':
production.state = 'rework'
@@ -437,44 +476,45 @@ class MrpProduction(models.Model):
# self.env['mrp.workorder'].json_workorder_str(k, production, route))
# 表面工艺工序
# 获取表面工艺id
if production.product_id.model_process_parameters_ids:
logging.info('model_process_parameters_ids:%s' % production.product_id.model_process_parameters_ids)
surface_technics_arr = []
# 工序id
route_workcenter_arr = []
for item in production.product_id.product_model_type_id.surface_technics_routing_tmpl_ids:
surface_technics_arr.append(item.route_workcenter_id.surface_technics_id.id)
route_workcenter_arr.append(item.route_workcenter_id.id)
if surface_technics_arr:
production_process_category = self.env['sf.production.process.category'].search(
[('production_process_ids.id', 'in', surface_technics_arr)],
order='sequence asc'
)
# 用filter刷选表面工艺id'是否存在工艺类别对象里
if production_process_category:
for p in production_process_category:
logging.info('production_process_category:%s' % p.name)
production_process = p.production_process_ids.filtered(
lambda pp: pp.id in surface_technics_arr)
if production_process:
process_parameter = production.product_id.model_process_parameters_ids.filtered(
lambda pm: pm.process_id.id == production_process.id)
if process_parameter:
# 产品为表面工艺服务的供应商
product_production_process = self.env['product.template'].search(
[('server_product_process_parameters_id', '=', process_parameter.id)])
if product_production_process:
route_production_process = self.env[
'mrp.routing.workcenter'].search(
[('surface_technics_id', '=', production_process.id),
('id', 'in', route_workcenter_arr)])
if route_production_process:
workorders_values.append(
self.env[
'mrp.workorder']._json_workorder_surface_process_str(
production, route_production_process,
process_parameter,
product_production_process.seller_ids[0].partner_id.id))
# 工序id
surface_technics_arr = []
route_workcenter_arr = []
for item in production.product_id.product_model_type_id.surface_technics_routing_tmpl_ids:
if item.route_workcenter_id.surface_technics_id.id:
for process_param in production.product_id.model_process_parameters_ids:
logging.info('process_param:%s%s' % (process_param.id, process_param.name))
if item.route_workcenter_id.surface_technics_id == process_param.process_id:
logging.info(
'surface_technics_id:%s%s' % (item.route_workcenter_id.surface_technics_id.id,
item.route_workcenter_id.surface_technics_id.name))
surface_technics_arr.append(item.route_workcenter_id.surface_technics_id.id)
route_workcenter_arr.append(item.route_workcenter_id.id)
if surface_technics_arr:
production_process = self.env['sf.production.process'].search(
[('id', 'in', surface_technics_arr)],
order='sequence asc'
)
for p in production_process:
logging.info('production_process:%s' % p.name)
# if production_process:
process_parameter = production.product_id.model_process_parameters_ids.filtered(
lambda pm: pm.process_id.id == p.id)
if process_parameter:
# 产品为表面工艺服务的供应商
product_production_process = self.env['product.template'].search(
[('server_product_process_parameters_id', '=', process_parameter.id)])
if product_production_process:
route_production_process = self.env[
'mrp.routing.workcenter'].search(
[('surface_technics_id', '=', p.id),
('id', 'in', route_workcenter_arr)])
if route_production_process:
workorders_values.append(
self.env[
'mrp.workorder']._json_workorder_surface_process_str(
production, route_production_process,
process_parameter,
product_production_process.seller_ids[0].partner_id.id))
elif production.product_id.categ_id.type == '坯料':
embryo_routing_workcenter = self.env['sf.embryo.model.type.routing.sort'].search(
[('embryo_model_type_id', '=', production.product_id.embryo_model_type_id.id)],
@@ -624,7 +664,7 @@ class MrpProduction(models.Model):
# 表面工艺工序
# 模型类型的表面工艺工序模版
surface_tmpl_ids = model_type_id.surface_technics_routing_tmpl_ids
# 产品选择的表面工艺
# 产品选择的表面工艺参数
model_process_parameters_ids = rec.product_id.model_process_parameters_ids
process_dict = {}
if model_process_parameters_ids:
@@ -633,7 +673,7 @@ class MrpProduction(models.Model):
for surface_tmpl_id in surface_tmpl_ids:
if process_id == surface_tmpl_id.route_workcenter_id.surface_technics_id:
surface_tmpl_name = surface_tmpl_id.route_workcenter_id.name
process_dict.update({int(process_id.category_id.code): '%s-%s' % (
process_dict.update({int(process_id.sequence): '%s-%s' % (
surface_tmpl_name, process_parameters_id.name)})
process_list = sorted(process_dict.keys())
for process_num in process_list:
@@ -650,14 +690,16 @@ class MrpProduction(models.Model):
raise ValidationError('该产品【加工面板】为空!')
else:
raise ValidationError('该产品没有选择【模版类型】!')
logging.info('sequence_list: %s' % sequence_list)
for work in rec.workorder_ids:
if sequence_list.get(work.name):
work.sequence = sequence_list[work.name]
work_name = work.name
logging.info(work_name)
if sequence_list.get(work_name):
work.sequence = sequence_list[work_name]
elif sequence_list.get(work.processing_panel):
processing_panel = sequence_list.get(work.processing_panel)
if processing_panel.get(work.name):
work.sequence = processing_panel[work.name]
if processing_panel.get(work_name):
work.sequence = processing_panel[work_name]
else:
raise ValidationError('工序【%s】在产品选择的模版类型中不存在!' % work.name)
else:
@@ -683,8 +725,9 @@ class MrpProduction(models.Model):
sequence_max += 1
panel_sequence_list.update({tmpl_id.route_workcenter_id.name: sequence_max})
for work_id in work_ids:
if panel_sequence_list.get(work_id.name):
work_id.sequence = panel_sequence_list[work_id.name]
work_name = work_id.name
if panel_sequence_list.get(work_name):
work_id.sequence = panel_sequence_list[work_name]
# 创建工单并进行排序
def _create_workorder(self, item):
@@ -692,6 +735,52 @@ class MrpProduction(models.Model):
self._reset_work_order_sequence()
return True
def process_range_time(self):
for production in self:
works = production.workorder_ids
pro_plan = self.env['sf.production.plan'].search([('production_id', '=', production.id)], limit=1)
if not pro_plan:
continue
type_map = {'装夹预调': False, 'CNC加工': False, '解除装夹': False}
# 最后一次加工结束时间
last_time = pro_plan.date_planned_start
# 预置时间
for work in works:
count = type_map.get(work.routing_type)
date_planned_end = None
date_planned_start = None
duration_expected = datetime.timedelta(minutes=work.duration_expected)
reserve_time = datetime.timedelta(minutes=work.reserved_duration)
if not count:
# 第一轮加工
if work.routing_type == '装夹预调':
date_planned_end = last_time - reserve_time
date_planned_start = date_planned_end - duration_expected
elif work.routing_type == 'CNC加工':
date_planned_start = last_time
date_planned_end = last_time + duration_expected
last_time = date_planned_end
else:
date_planned_start = last_time + reserve_time
date_planned_end = date_planned_start + duration_expected
last_time = date_planned_end
type_map.update({work.routing_type: True})
else:
date_planned_start = last_time + reserve_time
date_planned_end = date_planned_start + duration_expected
last_time = date_planned_end
work.leave_id.write({
'date_from': date_planned_start,
'date_to': date_planned_end,
})
# work.write({'date_planned_start': date_planned_start, 'date_planned_finished': date_planned_end})
work.date_planned_start = date_planned_start
work.date_planned_finished = date_planned_end
routing_workcenter = self.env['mrp.routing.workcenter'].sudo().search(
[('name', '=', work.routing_type)])
work.write({'date_planned_start': date_planned_start, 'date_planned_finished': date_planned_end,'duration_expected':routing_workcenter.time_cycle})
# 修改标记已完成方法
def button_mark_done1(self):
if not self.workorder_ids.filtered(lambda w: w.routing_type not in ['表面工艺']):
@@ -788,6 +877,23 @@ class MrpProduction(models.Model):
})
return action
# 报废
def button_scrap_new(self):
cloud_programming = self._cron_get_programming_state()
return {
'name': _('报废'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'sf.production.wizard',
'target': 'new',
'context': {
'default_production_id': self.id,
'default_reprogramming_num': cloud_programming['reprogramming_num'],
'default_programming_states': cloud_programming['programming_state'],
'default_is_reprogramming': True if cloud_programming['programming_state'] in ['已下发'] else False
}
}
# 返工
def button_rework(self):
cloud_programming = None
@@ -905,7 +1011,6 @@ class MrpProduction(models.Model):
# production.write(
# {'state': 'progress', 'programming_state': '已编程', 'is_rework': False})
# logging.info('返工含有已编程未下发的程序更新完成:%s' % production.name)
logging.info('更新程序完成:%s' % production.name)
else:
raise UserError(result['message'])
@@ -913,116 +1018,114 @@ class MrpProduction(models.Model):
logging.info('get_new_program error:%s' % e)
raise UserError("从云平台获取最新程序失败,请联系管理员")
def recreateManufacturing(self):
def recreateManufacturing(self, item):
"""
重新生成制造订单
"""
if self.is_scrap is True:
sale_order = self.env['sale.order'].sudo().search([('name', '=', productions.origin)])
values = self.env['mrp.production'].create_production1_values(self.production_id)
productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(
self.production_id.company_id).create(
values)
# self.env['stock.move'].sudo().create(productions._get_moves_raw_values())
self.env['stock.move'].sudo().create(productions._get_moves_finished_values())
productions._create_workorder()
productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \
(
p.move_dest_ids.procure_method != 'make_to_order' and
not p.move_raw_ids and not p.workorder_ids)).action_confirm()
for production_item in productions:
process_parameter_workorder = self.env['mrp.workorder'].search(
[('surface_technics_parameters_id', '!=', False), ('production_id', '=', production_item.id),
('is_subcontract', '=', True)])
if process_parameter_workorder:
is_pick = False
consecutive_workorders = []
m = 0
sorted_workorders = sorted(process_parameter_workorder, key=lambda w: w.id)
for i in range(len(sorted_workorders) - 1):
if m == 0:
is_pick = False
if sorted_workorders[i].supplier_id.id == sorted_workorders[i + 1].supplier_id.id and \
sorted_workorders[i].is_subcontract == sorted_workorders[i + 1].is_subcontract and \
sorted_workorders[i].id == sorted_workorders[i + 1].id - 1:
if sorted_workorders[i] not in consecutive_workorders:
consecutive_workorders.append(sorted_workorders[i])
consecutive_workorders.append(sorted_workorders[i + 1])
m += 1
continue
else:
if m == len(consecutive_workorders) - 1 and m != 0:
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders,
production_item)
if sorted_workorders[i] in consecutive_workorders:
is_pick = True
consecutive_workorders = []
m = 0
# 当前面的连续工序生成对应的外协出入库单再生成当前工序的外协出入库单
if is_pick is False:
self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i],
production_item)
if m == len(consecutive_workorders) - 1 and m != 0:
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders,
production_item)
if sorted_workorders[i] in consecutive_workorders:
is_pick = True
consecutive_workorders = []
m = 0
if m == len(consecutive_workorders) - 1 and m != 0:
self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, production_item)
if is_pick is False and m == 0:
if len(sorted_workorders) == 1:
self.env['stock.picking'].create_outcontract_picking(sorted_workorders, production_item)
else:
self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i], production_item)
for production in productions:
origin_production = production.move_dest_ids and production.move_dest_ids[
0].raw_material_production_id or False
orderpoint = production.orderpoint_id
if orderpoint and orderpoint.create_uid.id == SUPERUSER_ID and orderpoint.trigger == 'manual':
production.message_post(
body=_('This production order has been created from Replenishment Report.'),
message_type='comment',
subtype_xmlid='mail.mt_note')
elif orderpoint:
production.message_post_with_view(
'mail.message_origin_link',
values={'self': production, 'origin': orderpoint},
subtype_id=self.env.ref('mail.mt_note').id)
elif origin_production:
production.message_post_with_view(
'mail.message_origin_link',
values={'self': production, 'origin': origin_production},
subtype_id=self.env.ref('mail.mt_note').id)
'''
创建生产计划
'''
# 工单耗时
workorder_duration = 0
for workorder in productions.workorder_ids:
workorder_duration += workorder.duration_expected
if sale_order:
sale_order.mrp_production_ids |= productions
# sale_order.write({'schedule_status': 'to schedule'})
self.env['sf.production.plan'].sudo().with_company(self.production_id.company_id).create({
'name': productions.name,
'order_deadline': sale_order.deadline_of_delivery,
'production_id': productions.id,
'date_planned_start': productions.date_planned_start,
'origin': productions.origin,
'product_qty': productions.product_qty,
'product_id': productions.product_id.id,
'state': 'draft',
})
procurement_requests = []
sale_order = self.env['sale.order'].sudo().search([('name', '=', self.origin)])
values = self.env['mrp.production'].create_production1_values(self)
# productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(
# self.company_id).create(
# values)
# 查询出库移动记录
out_picking = self.env['stock.picking'].search(
[('origin', '=', sale_order.name), ('name', 'ilike', 'WH/OUT/')])
move = out_picking.move_ids.filtered(lambda pd: pd.product_id == self.product_id)
move_values = {'product_description_variants': '',
'date_planned': fields.Datetime.now(),
'date_deadline': fields.Datetime.now(),
'move_dest_ids': move,
'group_id': move.group_id,
'route_ids': [],
'warehouse_id': self.warehouse_id,
'priority': 0,
'orderpoint_id': False,
'product_packaging_id': False}
procurement_requests.append(self.env['procurement.group'].Procurement(
move.product_id, 1.0, move.product_uom,
move.location_id, move.rule_id and move.rule_id.name or "/",
sale_order.name, move.company_id, move_values))
self.env['procurement.group'].run(procurement_requests,
raise_user_error=not self.env.context.get('from_orderpoint'))
productions = self.env['mrp.production'].sudo().search(
[('origin', '=', self.origin)], order='id desc', limit=1)
move = self.env['stock.move'].search([('origin', '=', productions.name)], order='id desc')
for mo in move:
if mo.procure_method == 'make_to_order' and mo.name != productions.name:
if mo.name == '/':
domain = [('barcode', '=', 'WH-PC'), ('sequence_code', '=', 'PC')]
elif mo.name == '':
domain = [('barcode', '=', 'WH-INTERNAL'), ('sequence_code', '=', 'INT')]
picking_type = self.env['stock.picking.type'].search(domain)
mo.write({'picking_type_id': picking_type.id})
mo._assign_picking()
else:
if mo.reference != productions.name:
mo.reference = productions.name
if mo.production_id:
if mo.production_id != productions:
mo.production_id = False
mo_move = self.env['stock.move'].search(
[('origin', '=', sale_order.name), ('reference', 'ilike', 'WH/MO/')])
if mo_move:
sfp_move = self.env['stock.move'].search(
[('origin', '=', sale_order.name), ('reference', 'ilike', 'WH/SFP/')], limit=1)
mo_move.write({'reference': sfp_move.reference, 'partner_id': sfp_move.partner_id.id,
'picking_id': sfp_move.picking_id.id, 'picking_type_id': sfp_move.picking_type_id.id,
'production_id': False})
productions.write({'programming_no': self.programming_no, 'is_remanufacture': True})
# productions.procurement_group_id.mrp_production_ids.move_dest_ids.write(
# {'group_id': self.env['procurement.group'].search([('name', '=', sale_order.name)])})
stock_picking = None
pc_picking = self.env['stock.picking'].search(
[('origin', '=', productions.name), ('name', 'ilike', 'WH/PC/')])
stock_picking = pc_picking
int_picking = self.env['stock.picking'].search(
[('origin', '=', productions.name), ('name', 'ilike', 'WH/INT/')])
stock_picking |= int_picking
for pick in stock_picking:
if pick.move_ids:
product_type_id = pick.move_ids[0].product_id.categ_id
if product_type_id.name == '坯料':
location_id = self.env['stock.location'].search([('name', '=', '坯料存货区')])
if not location_id:
logging.info(f'没有搜索到【坯料存货区】: {location_id}')
break
if pick.picking_type_id.name == '内部调拨':
if pick.location_dest_id.product_type != product_type_id:
pick.location_dest_id = location_id.id
elif pick.picking_type_id.name == '生产发料':
if pick.location_id.product_type != product_type_id:
pick.location_id = location_id.id
scarp_process_parameter_workorder = self.env['mrp.workorder'].search(
[('surface_technics_parameters_id', '!=', False), ('production_id', '=', self.id),
('is_subcontract', '=', True)])
if scarp_process_parameter_workorder:
production_programming = self.env['mrp.production'].search(
[('programming_no', '=', self.programming_no), ('id', '!=', productions.id)], order='name asc')
production_list = [production.name for production in production_programming]
purchase_orders = self.env['purchase.order'].search([('origin', 'ilike', ','.join(production_list))])
for purchase_item in purchase_orders.order_line:
for process_item in scarp_process_parameter_workorder:
if purchase_item.product_id.categ_type == '表面工艺':
if purchase_item.product_id.server_product_process_parameters_id == process_item.surface_technics_parameters_id:
print(purchase_orders.origin.find(productions.name))
if purchase_orders.origin.find(productions.name) == -1:
purchase_orders.origin += ',' + productions.name
if item['is_reprogramming'] is False:
productions._create_workorder(item)
productions.programming_state = '已编程'
else:
productions.programming_state = '编程中'
return productions
# 在之前的销售单上重新生成制造订单
def create_production1_values(self, production, sale_order):
def create_production1_values(self, production):
production_values_str = {'origin': production.origin,
'product_id': production.product_id.id,
'programming_state': '已编程',
'product_description_variants': production.product_description_variants,
'product_qty': production.product_qty,
'product_uom_id': production.product_uom_id.id,
@@ -1032,7 +1135,8 @@ class MrpProduction(models.Model):
'date_deadline': production.date_deadline,
'date_planned_start': production.date_planned_start,
'date_planned_finished': production.date_planned_finished,
'procurement_group_id': sale_order.id,
# 'procurement_group_id': self.env["procurement.group"].create(
# {'name': production.name}).id,
'propagate_cancel': production.propagate_cancel,
'orderpoint_id': production.orderpoint_id.id,
'picking_type_id': production.picking_type_id.id,

View File

@@ -21,7 +21,7 @@ class ResMrpRoutingWorkcenter(models.Model):
workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_route', required=True)
bom_id = fields.Many2one('mrp.bom', required=False)
surface_technics_id = fields.Many2one('sf.production.process', string="表面工艺")
reserved_duration = fields.Float('预留时长', default=30, tracking=True)
def get_no(self):
international_standards = self.search(
[('code', '!=', ''), ('active', 'in', [True, False])],

View File

@@ -41,6 +41,7 @@ class ResWorkcenter(models.Model):
oee_target = fields.Float(
string='OEE Target', help="Overall Effective Efficiency Target in percentage", default=90, tracking=True)
oee = fields.Float(compute='_compute_oee', help='Overall Equipment Effectiveness, based on the last month', store=True)
time_start = fields.Float('Setup Time', tracking=True)
time_stop = fields.Float('Cleanup Time', tracking=True)
@@ -124,6 +125,8 @@ class ResWorkcenter(models.Model):
res[wc_id] = [(datetime.fromtimestamp(s), datetime.fromtimestamp(e)) for s, e, _ in final_intervals_wc]
return res
# AGV是否可配送
is_agv_scheduling = fields.Boolean(string="AGV所属区域", tracking=True)
class ResWorkcenterProductivity(models.Model):
_inherit = 'mrp.workcenter.productivity'

View File

@@ -58,9 +58,15 @@ class ResMrpWorkOrder(models.Model):
('cancel', '取消')], string='Status',
compute='_compute_state', store=True,
default='pending', copy=False, readonly=True, recursive=True, index=True, tracking=True)
# state = fields.Selection(selection_add=[('to be detected', "待检测"), ('rework', '返工')], tracking=True)
manual_quotation = fields.Boolean('人工编程', default=False, readonly=True)
@api.depends('production_id.manual_quotation')
def _compute_manual_quotation(self):
for item in self:
item.manual_quotation = item.production_id.manual_quotation
manual_quotation = fields.Boolean('人工编程', default=False, compute=_compute_manual_quotation, store=True)
def _compute_working_users(self):
super()._compute_working_users()
@@ -131,13 +137,32 @@ class ResMrpWorkOrder(models.Model):
is_subcontract = fields.Boolean(string='是否外协')
surface_technics_parameters_id = fields.Many2one('sf.production.process.parameter', string="表面工艺可选参数")
picking_ids = fields.Many2many('stock.picking', string='外协出入库单')
# purchase_id = fields.Many2one('purchase.order', string='外协采购单')
surface_technics_picking_count = fields.Integer("外协出入库", compute='_compute_surface_technics_picking_ids')
surface_technics_purchase_count = fields.Integer("外协采购", compute='_compute_surface_technics_purchase_ids')
# 是否绑定托盘
is_trayed = fields.Boolean(string='是否绑定托盘', default=False)
@api.depends('name', 'production_id.name')
def _compute_surface_technics_picking_ids(self):
for order in self:
picking_ids = self.env['stock.picking'].search([('id', 'in', order.picking_ids.ids)])
order.surface_technics_picking_count = len(picking_ids)
for workorder in self:
if workorder.routing_type == '表面工艺':
domain = [('origin', '=', workorder.production_id.name)]
previous_workorder = self.env['mrp.workorder'].search(
[('sequence', '=', workorder.sequence - 1), ('routing_type', '=', '表面工艺'),
('production_id', '=', workorder.production_id.id)])
if previous_workorder:
process_product = self.env['product.template']._get_process_parameters_product(
previous_workorder.surface_technics_parameters_id)
domain += [('partner_id', '=', process_product.partner_id.id)]
else:
domain += [('surface_technics_parameters_id', '=', workorder.surface_technics_parameters_id.id)]
picking_ids = self.env['stock.picking'].search(domain, order='id asc')
workorder.surface_technics_picking_count = len(picking_ids)
workorder.picking_ids = picking_ids.ids
else:
workorder.surface_technics_picking_count = 0
def action_view_surface_technics_picking(self):
self.ensure_one()
@@ -153,6 +178,38 @@ class ResMrpWorkOrder(models.Model):
action['context'] = dict(self._context, default_origin=self.name)
return action
@api.depends('state', 'production_id.name')
def _compute_surface_technics_purchase_ids(self):
for order in self:
if order.routing_type == '表面工艺':
production_programming = self.env['mrp.production'].search(
[('programming_no', '=', order.production_id.programming_no)], order='name asc')
production_no_remanufacture = production_programming.filtered(lambda a: a.is_remanufacture is False)
production_list = [production.name for production in production_programming]
purchase = self.env['purchase.order'].search([('origin', '=', ','.join(production_list))])
for line in purchase.order_line:
if line.product_id.server_product_process_parameters_id == order.surface_technics_parameters_id and line.product_qty == len(
production_no_remanufacture):
order.surface_technics_purchase_count = len(purchase)
else:
order.surface_technics_purchase_count = 0
def action_view_surface_technics_purchase(self):
self.ensure_one()
production_programming = self.env['mrp.production'].search(
[('programming_no', '=', self.production_id.programming_no)], order='name asc')
production_list = [production.name for production in production_programming]
purchase_orders = self.env['purchase.order'].search([('origin', '=', ','.join(production_list))])
result = {
"type": "ir.actions.act_window",
"res_model": "purchase.order",
"res_id": purchase_orders.id,
# "domain": [['id', 'in', self.purchase_id]],
"name": _("Purchase Orders"),
'view_mode': 'form',
}
return result
supplier_id = fields.Many2one('res.partner', string='外协供应商')
equipment_id = fields.Many2one('maintenance.equipment', string='加工设备', tracking=True)
# 保存名称
@@ -181,6 +238,7 @@ class ResMrpWorkOrder(models.Model):
tool_state = fields.Selection([('0', '正常'), ('1', '缺刀'), ('2', '无效刀')], string='功能刀具状态', default='0',
store=True, compute='_compute_tool_state')
tool_state_remark = fields.Text(string='功能刀具状态备注(缺刀)', compute='_compute_tool_state_remark', store=True)
reserved_duration = fields.Float('预留时长', default=30, tracking=True)
@api.depends('cnc_ids.tool_state')
def _compute_tool_state_remark(self):
@@ -318,10 +376,10 @@ class ResMrpWorkOrder(models.Model):
vals['leave_id'] = leave.id
self.write(vals)
@api.onchange('rfid_code')
def _onchange(self):
if self.rfid_code and self.state == 'progress':
self.workpiece_delivery_ids[0].write({'rfid_code': self.rfid_code})
# @api.onchange('rfid_code')
# def _onchange(self):
# if self.rfid_code and self.state == 'progress':
# self.workpiece_delivery_ids[0].write({'rfid_code': self.rfid_code})
def get_plan_workorder(self, production_line):
tomorrow = (date.today() + timedelta(days=+1)).strftime("%Y-%m-%d")
@@ -590,29 +648,36 @@ class ResMrpWorkOrder(models.Model):
# 拼接工单对象属性值
def json_workorder_str(self, k, production, route, item):
# 计算预计时长duration_expected
if route.routing_type == '切割':
duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
[('name', '=', '切割')]).time_cycle
# elif route.routing_type == '获取CNC加工程序':
routing_types = ['切割', '装夹预调', 'CNC加工', '解除装夹']
if route.routing_type in routing_types:
routing_workcenter = self.env['mrp.routing.workcenter'].sudo().search(
[('name', '=', route.routing_type)])
duration_expected = routing_workcenter.time_cycle
reserved_duration = routing_workcenter.reserved_duration
# if route.routing_type == '切割':
# duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
# [('name', '=', '获取CNC加工程序')]).time_cycle
elif route.routing_type == '装夹预调':
duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
[('name', '=', '装夹预调')]).time_cycle
# elif route.routing_type == '前置三元定位检测':
# [('name', '=', '切割')]).time_cycle
# # elif route.routing_type == '获取CNC加工程序':
# # duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
# # [('name', '=', '获取CNC加工程序')]).time_cycle
# elif route.routing_type == '装夹预调':
# duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
# [('name', '=', '前置三元定位检测')]).time_cycle
elif route.routing_type == 'CNC加工':
duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
[('name', '=', 'CNC加工')]).time_cycle
# elif route.routing_type == '后置三元质量检测':
# [('name', '=', '装夹预调')]).time_cycle
# # elif route.routing_type == '前置三元定位检测':
# # duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
# # [('name', '=', '前置三元定位检测')]).time_cycle
# elif route.routing_type == 'CNC加工':
# duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
# [('name', '=', '后置三元质量检测')]).time_cycle
elif route.routing_type == '解除装夹':
duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
[('name', '=', '解除装夹')]).time_cycle
# [('name', '=', 'CNC加工')]).time_cycle
# # elif route.routing_type == '后置三元质量检测':
# # duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
# # [('name', '=', '后置三元质量检测')]).time_cycle
# elif route.routing_type == '解除装夹':
# duration_expected = self.env['mrp.routing.workcenter'].sudo().search(
# [('name', '=', '解除装夹')]).time_cycle
else:
duration_expected = 60
reserved_duration = 30
workorders_values_str = [0, '', {
'product_uom_id': production.product_uom_id.id,
'qty_producing': 0,
@@ -634,25 +699,36 @@ class ResMrpWorkOrder(models.Model):
k, item),
'cmm_ids': False if route.routing_type != 'CNC加工' else self.env['sf.cmm.program']._json_cmm_program(k,
item),
'workpiece_delivery_ids': False if not route.routing_type == '装夹预调' else self._json_workpiece_delivery_list(
production)
# 'workpiece_delivery_ids': False if not route.routing_type == '装夹预调' else self._json_workpiece_delivery_list(
# production)
'reserved_duration': reserved_duration,
}]
return workorders_values_str
def _json_workpiece_delivery_list(self, production):
up_route = self.env['sf.agv.task.route'].search([('route_type', '=', '上产线')], limit=1, order='id asc')
down_route = self.env['sf.agv.task.route'].search([('route_type', '=', '下产线')], limit=1, order='id asc')
def _json_workpiece_delivery_list(self):
# 修改在装夹工单完成后,生成上产线的工件配送单
# up_route = self.env['sf.agv.task.route'].search([('route_type', '=', '上产线')], limit=1, order='id asc')
# down_route = self.env['sf.agv.task.route'].search([('route_type', '=', '下产线')], limit=1, order='id asc')
return [
[0, '',
{'production_id': production.id, 'production_line_id': production.production_line_id.id, 'type': '上产线',
'route_id': up_route.id,
'feeder_station_start_id': up_route.start_site_id.id,
'feeder_station_destination_id': up_route.end_site_id.id}],
[0, '',
{'production_id': production.id, 'production_line_id': production.production_line_id.id, 'type': '下产线',
'route_id': down_route.id,
'feeder_station_start_id': down_route.start_site_id.id,
'feeder_station_destination_id': down_route.end_site_id.id}]]
{
'production_id': self.production_id.id,
'production_line_id': self.production_id.production_line_id.id,
'type': '上产线',
'is_cnc_program_down': True,
'rfid_code': self.rfid_code
# 'route_id': up_route.id,
# 'feeder_station_start_id': agv_start_site_id,
# 'feeder_station_destination_id': up_route.end_site_id.id
}
],
# [0, '',
# {'production_id': production.id, 'production_line_id': production.production_line_id.id, 'type': '下产线',
# 'route_id': down_route.id,
# 'feeder_station_start_id': down_route.start_site_id.id,
# 'feeder_station_destination_id': down_route.end_site_id.id}]
]
# 拼接工单对象属性值(表面工艺)
def _json_workorder_surface_process_str(self, production, route, process_parameter, supplier_id):
@@ -868,7 +944,7 @@ class ResMrpWorkOrder(models.Model):
workorder.state = 'waiting'
elif workorder.routing_type == '解除装夹' and workorder.state not in ['done', 'rework', 'cancel']:
if cnc_workorder:
if not cnc_workorder_pending:
if not cnc_workorder_pending or unclamp_workorder.test_results == '报废':
workorder.state = 'waiting'
# else:
# if workorder.production_id.is_rework is True:
@@ -885,10 +961,26 @@ class ResMrpWorkOrder(models.Model):
# workorder.state = 'ready'
if workorder.routing_type == '表面工艺' and workorder.state not in ['done', 'progress']:
if unclamp_workorder:
workorder.state = 'ready'
# else:
# if workorder.state not in ['cancel', 'rework']:
# workorder.state = 'rework'
if workorder.is_subcontract is False:
workorder.state = 'ready'
else:
production_programming = self.env['mrp.production'].search(
[('programming_no', '=', self.production_id.programming_no)], order='name asc')
production_no_remanufacture = production_programming.filtered(
lambda a: a.is_remanufacture is False)
production_list = [production.name for production in production_programming]
purchase_orders = self.env['purchase.order'].search(
[('origin', 'ilike', ','.join(production_list))])
for line in purchase_orders.order_line:
if line.product_id.server_product_process_parameters_id == workorder.surface_technics_parameters_id and line.product_qty == len(
production_no_remanufacture):
if purchase_orders.state == 'purchase':
workorder.state = 'ready'
else:
workorder.state = 'waiting'
elif workorder.production_id.state == 'scrap':
if workorder.routing_type == '解除装夹' and unclamp_workorder.test_results == '报废':
workorder.state = 'waiting'
if workorder.routing_type == '装夹预调' and workorder.state in ['waiting', 'ready', 'pending']:
workorder_ids = workorder.production_id.workorder_ids
work_bo = True
@@ -1007,7 +1099,7 @@ class ResMrpWorkOrder(models.Model):
('location_dest_id', '=', self.env['stock.location'].search(
[('barcode', 'ilike', 'VL-SPOC')]).id),
('origin', '=', self.production_id.name)])
if move_out:
if move_out.state != 'done':
move_out.write({'state': 'assigned'})
self.env['stock.move.line'].create(move_out.get_move_line(self.production_id, self))
@@ -1074,23 +1166,31 @@ class ResMrpWorkOrder(models.Model):
def button_finish(self):
for record in self:
if record.routing_type == '装夹预调':
if not record.material_center_point and record.X_deviation_angle > 0:
raise UserError("请对前置三元检测定位参数进行计算定位")
if not record.rfid_code and record.is_rework is False:
raise UserError("请扫RFID码进行绑定")
if record.is_rework is False:
if not record.material_center_point and record.X_deviation_angle > 0:
raise UserError("坯料中心点为空或X偏差角度小于等于0")
record.process_state = '待加工'
# record.write({'process_state': '待加工'})
record.production_id.process_state = '待加工'
# 生成工件配送单
record.workpiece_delivery_ids = record._json_workpiece_delivery_list()
if record.routing_type == 'CNC加工':
record.process_state = '待解除装夹'
# record.write({'process_state': '待加工'})
record.production_id.process_state = '待解除装夹'
self.env['sf.production.plan'].sudo().search([('name', '=', record.production_id.name)]).write({
'state': 'finished',
'actual_end_time': datetime.now()
})
record.production_id.write({'detection_result_ids': [(0, 0, {
'rework_reason': record.reason,
'detailed_reason': record.detailed_reason,
'processing_panel': record.processing_panel,
'routing_type': record.routing_type,
'handle_result': '待处理' if record.test_results == '返工' or record.is_rework is True else '',
'handle_result': '待处理' if record.test_results in ['返工',
'报废'] or record.is_rework is True else '',
'test_results': record.test_results,
'test_report': record.detection_report})],
'is_scrap': True if record.test_results == '报废' else False})
@@ -1110,28 +1210,13 @@ class ResMrpWorkOrder(models.Model):
picking_out = record.env['stock.move.line'].search(
[('picking_id', '=', record.picking_ids[0].id)])
logging.info('picking_out:%s' % picking_out.picking_id.name)
if picking_out:
order_line_ids = []
logging.info('surface_technics_parameters_id:%s' % record.surface_technics_parameters_id.name)
server_product = self.env['product.template'].search(
[('server_product_process_parameters_id', '=', record.surface_technics_parameters_id.id),
('detailed_type', '=', 'service')])
logging.info('server_product:%s' % server_product.name)
if server_product:
order_line_ids.append((0, 0, {
'product_id': server_product.product_variant_id.id,
'product_qty': 1,
'product_uom': server_product.uom_id.id
}))
self.env['purchase.order'].sudo().create({
'partner_id': server_product.seller_ids.partner_id.id,
'origin': record.production_id.name,
'state': 'draft',
'order_line': order_line_ids,
})
else:
raise UserError(
'请先在产品中配置表面工艺为%s相关的外协服务产品' % item.surface_technics_parameters_id.name)
# if picking_out:
# order_line_ids = []
# logging.info('surface_technics_parameters_id:%s' % record.surface_technics_parameters_id.name)
#
# else:
# raise UserError(
# '请先在产品中配置表面工艺为%s相关的外协服务产品' % item.surface_technics_parameters_id.name)
tem_date_planned_finished = record.date_planned_finished
tem_date_finished = record.date_finished
logging.info('routing_type:%s' % record.routing_type)
@@ -1183,6 +1268,17 @@ class ResMrpWorkOrder(models.Model):
record.production_id.button_mark_done1()
# record.production_id.state = 'done'
# 解绑托盘
def unbind_tray(self):
self.production_id.workorder_ids.write({
'rfid_code': False,
'tray_serial_number': False,
'tray_product_id': False,
'tray_brand_id': False,
'tray_type_id': False,
'tray_model_id': False,
'is_trayed': False})
# 将FTP的检测报告文件下载到临时目录
def download_reportfile_tmp(self, workorder, reportpath):
logging.info('reportpath/ftp地址:%s' % reportpath)
@@ -1222,6 +1318,66 @@ class ResMrpWorkOrder(models.Model):
else:
raise UserError("无关联制造订单或关联序列号,无法打印。请检查!")
@api.model
def get_views(self, views, options=None):
res = super().get_views(views, options)
if res['views'].get('list', {}) and self.env.context.get('search_default_workcenter_id'):
workcenter = self.env['mrp.workcenter'].browse(self.env.context.get('search_default_workcenter_id'))
tree_view = res['views']['list']
if workcenter.name == '工件拆卸中心':
arch = etree.fromstring(tree_view['arch'])
# 查找 tree 标签
tree_element = arch.xpath("//tree")[0]
# 查找或创建 header 标签
header_element = tree_element.find('header')
if header_element is None:
header_element = etree.Element('header')
tree_element.insert(0, header_element)
# 创建并添加按钮元素
button_element = etree.Element('button', {
'name': 'button_delivery',
'type': 'object',
'string': '解除装夹',
'class': 'btn-primary',
# 'className': 'btn-primary',
'modifiers': '{"force_show": 1}'
})
header_element.append(button_element)
# 更新 tree_view 的 arch
tree_view['arch'] = etree.tostring(arch, encoding='unicode')
return res
def button_delivery(self):
production_ids = []
workorder_ids = []
delivery_type = '运送空料架'
max_num = 4 # 最大配送数量
if len(self) > max_num:
raise UserError('仅限于拆卸1-4个制造订单请重新选择')
for item in self:
if item.state != 'ready':
raise UserError('请选择状态为【就绪】的工单进行解除装夹')
production_ids.append(item.production_id.id)
workorder_ids.append(item.id)
return {
'name': _('确认'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'sf.workpiece.delivery.wizard',
'target': 'new',
'context': {
# 'default_delivery_ids': [(6, 0, delivery_ids)],
'default_production_ids': [(6, 0, production_ids)],
'default_delivery_type': delivery_type,
'default_workorder_ids': [(6, 0, workorder_ids)],
'default_workcenter_id': self.env.context.get('default_workcenter_id'),
'default_confirm_button': '确认解除'
}}
class CNCprocessing(models.Model):
_name = 'sf.cnc.processing'
@@ -1430,6 +1586,7 @@ class SfWorkOrderBarcodes(models.Model):
raise UserError('该Rfid【%s】绑定的是【%s】, 不是托盘!!!' % (barcode, lot.product_id.name))
self.process_state = '待检测'
self.date_start = datetime.now()
self.is_trayed = True
else:
raise UserError('没有找到Rfid为【%s】的托盘信息!!!' % barcode)
# stock_move_line = self.env['stock.move.line'].search([('lot_name', '=', barcode)])
@@ -1501,20 +1658,24 @@ class WorkPieceDelivery(models.Model):
feeder_station_destination_id = fields.Many2one('sf.agv.site', '目的接驳站')
task_delivery_time = fields.Datetime('任务下发时间')
task_completion_time = fields.Datetime('任务完成时间')
type = fields.Selection(
[('上产线', '上产线'), ('下产线', '下产线'), ('运送空料架', '运送空料架')], string='类型')
def _get_agv_route_type_selection(self):
return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection']
type = fields.Selection(selection=_get_agv_route_type_selection, string='类型')
delivery_duration = fields.Float('配送时长', compute='_compute_delivery_duration')
status = fields.Selection(
[('待下发', '待下发'), ('待配送', '待配送'), ('已配送', '已配送'), ('已取消', '已取消')], string='状态',
default='待下发',
tracking=True)
[('待下发', '待下发'), ('已下发', '待配送'), ('已配送', '已配送'), ('已取消', '已取消')], string='状态',
default='待下发', tracking=True)
is_cnc_program_down = fields.Boolean('程序是否下发', default=False, tracking=True)
is_manual_work = fields.Boolean('人工操作', default=False)
active = fields.Boolean(string="有效", default=True)
agv_scheduling_id = fields.Many2one('sf.agv.scheduling', 'AGV任务调度')
@api.model
def create(self, vals):
if vals['route_id'] and vals.get('type') is None:
if vals.get('route_id') and vals.get('type') is None:
vals['type'] = '运送空料架'
else:
if vals.get('name', '/') == '/' or vals.get('name', '/') is False:
@@ -1526,14 +1687,14 @@ class WorkPieceDelivery(models.Model):
obj.feeder_station_start_id.name, obj.feeder_station_destination_id.name)
return obj
@api.constrains('route_id')
def _check_route_id(self):
if self.type == '运送空料架':
if self.route_id and self.name is False:
route = self.sudo().search(
[('route_id', '=', self.route_id.id), ('id', '!=', self.id), ('name', 'ilike', '运送空料架路线')])
if route:
raise UserError("该任务路线已存在,请重新选择")
# @api.constrains('route_id')
# def _check_route_id(self):
# if self.type == '运送空料架':
# if self.route_id and self.name is False:
# route = self.sudo().search(
# [('route_id', '=', self.route_id.id), ('id', '!=', self.id), ('name', 'ilike', '运送空料架路线')])
# if route:
# raise UserError("该任务路线已存在,请重新选择")
# @api.constrains('name')
# def _check_name(self):
@@ -1562,84 +1723,44 @@ class WorkPieceDelivery(models.Model):
def button_delivery(self):
delivery_ids = []
production_ids = []
workorder_ids = []
is_cnc_down = 0
is_not_production_line = 0
is_not_route = 0
same_production_line_id = None
same_route_id = None
down_status = '待下发'
production_type = None
num = 0
delivery_type = '上产线'
max_num = 4 # 最大配送数量
if len(self) > max_num:
raise UserError('仅限于配送1-4个制造订单请重新选择')
for item in self:
num += 1
if production_type is None:
production_type = item.type
if item.type == "运送空料架":
if num >= 2:
raise UserError('仅选择一条路线进行配送,请重新选择')
else:
delivery_ids.append(item.id)
else:
if num > 4:
raise UserError('仅限于配送1-4个制造订单请重新选择')
if item.status in ['待配送', '已配送']:
raise UserError('请选择状态为【待下发】的制造订单进行配送')
if item.route_id:
if same_route_id is None:
same_route_id = item.route_id.id
if item.route_id.id != same_route_id:
is_not_route += 1
# else:
# raise UserError('请选择【任务路线】再进行配送')
# if item.production_id.production_line_state == '已下产线' and item.state == '待下发' and item.type == '下产线':
# raise UserError('该制造订单已下产线,无需配送')
if production_type != item.type:
raise UserError('请选择类型为%s的制造订单进行配送' % production_type)
if down_status != item.status:
up_workpiece = self.search([('type', '=', '上产线'), ('production_id', '=', item.production_id),
('status', '=', '待下发')])
if up_workpiece:
raise UserError('您所选择的制造订单暂未上产线,请在上产线后再进行配送')
else:
raise UserError('请选择状态为【待下发】的制造订单进行配送')
if same_production_line_id is None:
same_production_line_id = item.production_line_id.id
if item.production_line_id.id != same_production_line_id:
is_not_production_line += 1
if item.is_cnc_program_down is False:
is_cnc_down += 1
if is_cnc_down == 0 and is_not_production_line == 0 and is_not_route == 0:
delivery_ids.append(item.id)
production_ids.append(item.production_id.id)
if item.status != '待下发':
raise UserError('请选择状态为【待下发】的制造订单进行配送')
if same_production_line_id is None:
same_production_line_id = item.production_line_id.id
if item.production_line_id.id != same_production_line_id:
is_not_production_line += 1
if item.is_cnc_program_down is False:
is_cnc_down += 1
if is_cnc_down == 0 and is_not_production_line == 0:
delivery_ids.append(item.id)
production_ids.append(item.production_id.id)
workorder_ids.append(item.workorder_id.id)
if is_cnc_down >= 1:
raise UserError('您所选择制造订单的【CNC程序】暂未下发请在程序下发后再进行配送')
if is_not_production_line >= 1:
raise UserError('您所选择制造订单的【目的生产线】不一致,请重新确认')
if is_not_route >= 1:
raise UserError('您所选择制造订单的【任务路线】不一致,请重新确认')
is_free = self._check_avgsite_state()
if is_free is True:
if delivery_ids:
return {
'name': _('确认'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'sf.workpiece.delivery.wizard',
'target': 'new',
'context': {
'default_delivery_ids': [(6, 0, delivery_ids)],
'default_production_ids': [(6, 0, production_ids)],
'default_destination_production_line_id': same_production_line_id,
'default_route_id': same_route_id,
'default_type': production_type,
}}
else:
if production_type == '运送空料架':
raise UserError("您所选择的【任务路线】的【终点接驳站】已占用,请在该接驳站空闲时进行配送")
else:
raise UserError(
"您所选择制造订单的【任务路线】的【终点接驳站】已占用,请在该接驳站空闲时或选择其他路线进行配送")
return {
'name': _('确认'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'sf.workpiece.delivery.wizard',
'target': 'new',
'context': {
'default_delivery_ids': [(6, 0, delivery_ids)],
'default_production_ids': [(6, 0, production_ids)],
'default_delivery_type': delivery_type,
'default_workorder_ids': [(6, 0, workorder_ids)],
'default_confirm_button': '确认配送'
}}
# 验证agv站点是否可用
def _check_avgsite_state(self):

View File

@@ -5,8 +5,10 @@ import base64
import hashlib
import os
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
from odoo.exceptions import ValidationError, UserError
from odoo.modules import get_resource_path
from OCC.Extend.DataExchange import read_step_file
from OCC.Extend.DataExchange import write_stl_file
@@ -106,6 +108,15 @@ class ResProductMo(models.Model):
name = fields.Char('产品名称', compute='_compute_tool_name', store=True, required=False)
@api.constrains('seller_ids')
def _check_seller_ids(self):
if self.categ_type == '表面工艺':
if self.seller_ids:
if self.seller_ids[0].price == 0.0:
raise UserError("请在该产品【采购】中的【价格】进行输入")
else:
raise UserError("请在【采购】中输入供应商信息")
@api.depends('cutting_tool_model_id', 'specification_id')
def _compute_tool_name(self):
for item in self:
@@ -113,6 +124,10 @@ class ResProductMo(models.Model):
name = '%s%s' % (item.cutting_tool_model_id.name, item.specification_id.name)
item.name = name
def _get_process_parameters_product(self, production_process):
return self.env['product.template'].search(
[('server_product_process_parameters_id', '=', production_process.id)]).seller_ids[0]
@api.onchange('cutting_tool_model_id')
def _onchange_cutting_tool_model_id(self):
for item in self:
@@ -640,6 +655,10 @@ class ResProductMo(models.Model):
'part_number': item.get('part_number') or '',
'active': True,
}
tax_id = self.env['account.tax'].sudo().search(
[('type_tax_use', '=', 'sale'), ('amount', '=', item.get('tax')), ('price_include', '=', 'True')])
if tax_id:
vals.update({'taxes_id': [(6, 0, [int(tax_id)])]})
copy_product_id.sudo().write(vals)
product_id.product_tmpl_id.active = False
return copy_product_id
@@ -736,7 +755,11 @@ class ResProductMo(models.Model):
# 产品名称唯一性校验
for item in templates:
if len(self.search([('name', '=', item.name)])) > 1:
raise ValidationError('产品名称【%s】已存在' % item.name)
raise UserError('产品名称【%s】已存在' % item.name)
if item.categ_type == '表面工艺':
if len(self.search([('server_product_process_parameters_id', '=',
item.server_product_process_parameters_id.id)])) > 1:
raise UserError('表面工艺参数为【%s】的产品已存在' % item.server_product_process_parameters_id.name)
if "create_product_product" not in self._context:
templates._create_variant_ids()
@@ -800,7 +823,7 @@ class ResProductFixture(models.Model):
diameter = fields.Float('直径(mm)', digits=(16, 2))
# '零点卡盘' 字段
weight = fields.Float('重量(mm)', digits=(16, 2))
weight = fields.Float('重量(kg)', digits=(16, 2))
orientation_dish_diameter = fields.Float('定位盘直径(mm)', digits=(16, 2))
clamping_diameter = fields.Float('装夹直径(mm)', digits=(16, 2))
clamping_num = fields.Selection([('1', '1'), ('2', '2'), ('4', '4'), ('6', '6'), ('8', '8')], string='装夹单元数')
@@ -947,6 +970,7 @@ class SfMaintenanceEquipmentAndProductTemplate(models.Model):
raise ValidationError("机床基坐标获取失败")
class SfMaintenanceEquipmentTool(models.Model):
_name = 'maintenance.equipment.tool'
_description = '机床刀位'

View File

@@ -0,0 +1,22 @@
from odoo import models, fields, api
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
is_agv_task_dispatch = fields.Boolean('是否下发AGV任务', default=False)
@api.model
def get_values(self):
values = super(ResConfigSettings, self).get_values()
config = self.env['ir.config_parameter'].sudo()
is_agv_task_dispatch = config.get_param('is_agv_task_dispatch')
values.update(
is_agv_task_dispatch=is_agv_task_dispatch,
)
return values
def set_values(self):
super(ResConfigSettings, self).set_values()
config = self.env['ir.config_parameter'].sudo()
config.set_param("is_agv_task_dispatch", self.is_agv_task_dispatch or False)

View File

@@ -68,6 +68,7 @@ class StockRule(models.Model):
@api.model
def _run_pull(self, procurements):
logging.info(procurements)
moves_values_by_company = defaultdict(list)
mtso_products_by_locations = defaultdict(list)
@@ -168,7 +169,6 @@ class StockRule(models.Model):
else:
forecasted_qties_by_loc[rule.location_src_id][procurement.product_id.id] -= qty_needed
procure_method = 'make_to_stock'
move_values = rule._get_stock_move_values(*procurement)
move_values['procure_method'] = procure_method
moves_values_by_company[procurement.company_id.id].append(move_values)
@@ -176,10 +176,10 @@ class StockRule(models.Model):
for company_id, moves_values in moves_values_by_company.items():
# create the move as SUPERUSER because the current user may not have the rights to do it (mto product
# launched by a sale for example)
moves = self.env['stock.move'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(moves_values)
moves = self.env['stock.move'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(
moves_values)
# Since action_confirm launch following procurement_group we should activate it.
moves._action_confirm()
return True
@api.model
@@ -217,6 +217,23 @@ class StockRule(models.Model):
(
p.move_dest_ids.procure_method != 'make_to_order' and not
p.move_raw_ids and not p.workorder_ids)).action_confirm()
# 处理 根据制造订单生成的采购单坯料入库时到原材料库,手动将原材料位置该为坯料存货区
for production in productions:
if production.picking_ids:
product_type_id = production.picking_ids[0].move_ids[0].product_id.categ_id
if product_type_id.name == '坯料':
location_id = self.env['stock.location'].search([('name', '=', '坯料存货区')])
if not location_id:
logging.info(f'没有搜索到【坯料存货区】: {location_id}')
break
for picking_id in production.picking_ids:
if picking_id.picking_type_id.name == '内部调拨':
if picking_id.location_dest_id.product_type != product_type_id:
picking_id.location_dest_id = location_id.id
elif picking_id.picking_type_id.name == '生产发料':
if picking_id.location_id.product_type != product_type_id:
picking_id.location_id = location_id.id
for production in productions:
'''
创建制造订单时生成序列号
@@ -271,14 +288,70 @@ class StockRule(models.Model):
# 为同一个product_id创建一个生产订单名称列表
product_id_to_production_names[product_id] = [production.name for production in all_production]
for production_item in productions:
production_programming = self.env['mrp.production'].search(
[('product_id.id', '=', production_item.product_id.id),
('origin', '=', production_item.origin)],
limit=1, order='id asc')
if production_item.product_id.id in product_id_to_production_names:
if not production_programming.programming_no:
if production_item.product_id.model_process_parameters_ids:
is_purchase = False
sorted_process_parameters = sorted(production_item.product_id.model_process_parameters_ids,
key=lambda w: w.id)
consecutive_process_parameters = []
m = 0
for i in range(len(sorted_process_parameters) - 1):
if m == 0:
is_purchase = False
if self.env['product.template']._get_process_parameters_product(
sorted_process_parameters[i]).partner_id == self.env[
'product.template']._get_process_parameters_product(sorted_process_parameters[
i + 1]).partner_id and \
sorted_process_parameters[i].gain_way == '外协':
if sorted_process_parameters[i] not in consecutive_process_parameters:
consecutive_process_parameters.append(sorted_process_parameters[i])
consecutive_process_parameters.append(sorted_process_parameters[i + 1])
m += 1
continue
else:
if m == len(consecutive_process_parameters) - 1 and m != 0:
self.env['purchase.order'].get_purchase_order(consecutive_process_parameters,
production_item,
product_id_to_production_names)
if sorted_process_parameters[i] in consecutive_process_parameters:
is_purchase = True
consecutive_process_parameters = []
m = 0
# 当前面的连续外协采购单生成再生成当前外协采购单
if is_purchase is False:
self.env['purchase.order'].get_purchase_order(consecutive_process_parameters,
production_item,
product_id_to_production_names)
if m == len(consecutive_process_parameters) - 1 and m != 0:
self.env['purchase.order'].get_purchase_order(consecutive_process_parameters,
production_item,
product_id_to_production_names)
if sorted_process_parameters[i] in consecutive_process_parameters:
is_purchase = True
consecutive_process_parameters = []
m = 0
if m == len(consecutive_process_parameters) - 1 and m != 0:
self.env['purchase.order'].get_purchase_order(consecutive_process_parameters,
production_item,
product_id_to_production_names)
if is_purchase is False and m == 0:
if len(sorted_process_parameters) == 1:
self.env['purchase.order'].get_purchase_order(sorted_process_parameters,
production_item,
product_id_to_production_names)
else:
self.env['purchase.order'].get_purchase_order(sorted_process_parameters[i],
production_item,
product_id_to_production_names)
# # 同一个产品多个制造订单对应一个编程单和模型库
# # 只调用一次fetchCNC并将所有生产订单的名称作为字符串传递
if not production_item.programming_no:
production_programming = self.env['mrp.production'].search(
[('product_id.id', '=', production_item.product_id.id),
('origin', '=', production_item.origin)],
limit=1, order='id asc')
if not production_programming.programming_no:
production_item.fetchCNC(
', '.join(product_id_to_production_names[production_item.product_id.id]))
@@ -368,7 +441,7 @@ class ProductionLot(models.Model):
if product.tracking == "serial":
last_serial = self.env['stock.lot'].search(
[('company_id', '=', company.id), ('product_id', '=', product.id)],
limit=1, order='id DESC')
limit=1, order='name desc')
if last_serial:
if product.categ_id.name == '刀具':
return self.env['stock.lot'].get_tool_generate_lot_names1(company, product)
@@ -468,12 +541,11 @@ class ProductionLot(models.Model):
class StockPicking(models.Model):
_inherit = 'stock.picking'
# workorder_in_id = fields.One2many('mrp.workorder', 'picking_in_id')
# workorder_out_id = fields.One2many('mrp.workorder', 'picking_out_id')
surface_technics_parameters_id = fields.Many2one('sf.production.process.parameter', string="表面工艺可选参数")
# 设置外协出入单的名称
def _get_name_Res(self, rescode):
last_picking = self.sudo().search([('name', 'like', rescode)], order='create_date desc,id desc', limit=1)
last_picking = self.sudo().search([('name', 'ilike', rescode)], order='create_date desc,id desc', limit=1)
if not last_picking:
num = "%04d" % 1
else:
@@ -499,7 +571,7 @@ class StockPicking(models.Model):
[('barcode', 'ilike', 'WH-PREPRODUCTION')]).id),
('location_id', '=', self.env['stock.location'].search(
[('barcode', 'ilike', 'VL-SPOC')]).id),
('origin', '=', self.origin)])
('origin', '=', self.origin), ('picking_id', '=', self.id)])
if self.location_id == move_in.location_id and self.location_dest_id == move_in.location_dest_id:
if move_out.origin == move_in.origin:
if move_out.picking_id.state != 'done':
@@ -516,7 +588,7 @@ class StockPicking(models.Model):
[('barcode', 'ilike', 'VL-SPOC')]).id),
('origin', '=', self.origin)])
production = self.env['mrp.production'].search([('name', '=', self.origin)])
if move_in:
if move_in.state != 'done':
move_in.write({'state': 'assigned'})
self.env['stock.move.line'].create(move_in.get_move_line(production, None))
@@ -526,7 +598,7 @@ class StockPicking(models.Model):
def create_outcontract_picking(self, sorted_workorders_arr, item):
m = 0
for sorted_workorders in sorted_workorders_arr:
pick_ids = []
# pick_ids = []
if m == 0:
outcontract_stock_move = self.env['stock.move'].search(
[('workorder_id', '=', sorted_workorders.id), ('production_id', '=', item.id)])
@@ -545,7 +617,7 @@ class StockPicking(models.Model):
outcontract_picking_type_out))
picking_out = self.create(
moves_out._get_new_picking_values_Res(item, sorted_workorders, 'WH/OCOUT/'))
pick_ids.append(picking_out.id)
# pick_ids.append(picking_out.id)
moves_out.write(
{'picking_id': picking_out.id, 'state': 'waiting', 'workorder_id': sorted_workorders.id})
moves_out._assign_picking_post_process(new=new_picking)
@@ -554,12 +626,12 @@ class StockPicking(models.Model):
outcontract_picking_type_in))
picking_in = self.create(
moves_in._get_new_picking_values_Res(item, sorted_workorders, 'WH/OCIN/'))
pick_ids.append(picking_in.id)
# pick_ids.append(picking_in.id)
moves_in.write(
{'picking_id': picking_in.id, 'state': 'waiting', 'workorder_id': sorted_workorders.id})
moves_in._assign_picking_post_process(new=new_picking)
m += 1
sorted_workorders.write({'picking_ids': [(6, 0, pick_ids)]})
# sorted_workorders.write({'picking_ids': [(6, 0, pick_ids)]})
class ReStockMove(models.Model):
@@ -590,6 +662,7 @@ class ReStockMove(models.Model):
return {
'name': self.env['stock.picking']._get_name_Res(rescode),
'origin': item.name,
'surface_technics_parameters_id': sorted_workorders.surface_technics_parameters_id.id,
'company_id': self.mapped('company_id').id,
'user_id': False,
'move_type': self.mapped('group_id').move_type or 'direct',

View File

@@ -150,5 +150,12 @@ access_sf_processing_panel_group_sf_order_user,sf_processing_panel_group_sf_orde
access_sf_production_wizard_group_sf_order_user,sf_production_wizard_group_sf_order_user,model_sf_production_wizard,sf_base.group_sf_order_user,1,1,1,0
access_sf_processing_panel_group_plan_dispatch,sf_processing_panel_group_plan_dispatch,model_sf_processing_panel,sf_base.group_plan_dispatch,1,1,1,0
access_sf_agv_scheduling_admin,sf_agv_scheduling_admin,model_sf_agv_scheduling,base.group_system,1,1,1,1
access_sf_agv_scheduling_group_sf_order_user,sf_agv_scheduling_group_sf_order_user,model_sf_agv_scheduling,sf_base.group_sf_order_user,1,1,1,0
access_sf_agv_scheduling_group_sf_mrp_manager,sf_agv_scheduling_group_sf_mrp_manager,model_sf_agv_scheduling,sf_base.group_sf_mrp_manager,1,1,1,0
access_sf_agv_scheduling_group_sf_equipment_user,sf_agv_scheduling_group_sf_equipment_user,model_sf_agv_scheduling,sf_base.group_sf_equipment_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
150
151
152
153
154
155
156
157
158
159
160
161

View File

@@ -1,16 +1,36 @@
var RFID = ''
$(document).off('keydown')
console.log(2222)
$(document).on('keydown', '.modal.d-block.o_technical_modal,body.o_web_client', function (e) {
const dom = $('.customRFID')
if(!dom.length) return
$(document).on('keydown', 'body.o_web_client', function (e) {
setTimeout(() => {
RFID = ''
}, 200)
if(e.key == 'Enter' && e.keyCode == 13 || e.key == 'Tab' && e.keyCode == 9){
if(!RFID || RFID.length <= 3) return;
dom.children('span').text(RFID)
RFID = ''
let fieldValue1 = $('[name="routing_type"]');
console.log('字段值:', fieldValue1.text());
console.log(RFID)
let fieldValue2 = $('[name="rfid_code"]');
console.log('字段值2:', fieldValue2.text());
// if(!RFID || RFID.length <= 3) return;
// $('[name="button_start"]').trigger('click')
// setTimeout(() => {
// $('.o_dialog .modal-footer .btn-primary').trigger('click')
// }, 50)
// RFID = ''
// return;
// fieldValue2.val() === '')
// 检查字段值是否等于“装夹预调”
if (fieldValue1.text() === '装夹预调') {
if (!RFID || RFID.length <= 3) return;
$('[name="button_start"]').trigger('click');
setTimeout(() => {
$('.o_dialog .modal-footer .btn-primary').trigger('click');
}, 100);
}
RFID = '';
return;
}
RFID += e.key

View File

@@ -0,0 +1,49 @@
odoo.define('sf_manufacturing.action_dispatch_confirm', function (require) {
const core = require('web.core');
const ajax = require('web.ajax');
const Dialog = require('web.Dialog');
var rpc = require('web.rpc');
var _t = core._t;
async function dispatch_confirm(parent, {params}) {
const dialog = new Dialog(parent, {
title: "确认",
$content: $('<div>').append("请确认是否仅配送" + params.workorder_count + "个工件?"),
buttons: [
{ text: "确认", classes: 'btn-primary', close: true, click: () => dispatchConfirmed(parent, params) },
{ text: "取消", close: true },
],
});
dialog.open();
async function dispatchConfirmed(parent, params) {
console.log(parent, 'parent')
rpc.query({
model: 'sf.workpiece.delivery.wizard',
method: 'confirm',
args: [params.active_id]
,
kwargs: {
context: params.context,
}
}).then(res => {
parent.services.action.doAction({
'type': 'ir.actions.client',
'tag': 'display_notification',
'target': 'new',
'params': {
'message': '任务下发成功AGV任务调度编号为【' + res.name + '】',
'type': 'success',
'sticky': false,
'next': {'type': 'ir.actions.act_window_close'},
}
});
})
}
}
core.action_registry.add('dispatch_confirm', dispatch_confirm);
return dispatch_confirm;
});

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" ?>
<templates xml:space="preserve">
<t t-name="sf_manufacturing.button_show" t-inherit="web.ListView.Buttons" t-inherit-mode="extension" owl="1">
<xpath expr="//div/t[@t-if='nbSelected']" position="after">
<t t-elif="!nbSelected">
<t t-foreach="archInfo.headerButtons" t-as="button" t-key="button.id">
<t t-if="button.modifiers.force_show">
<ListViewHeaderButton
list="model.root"
clickParams="button.clickParams"
defaultRank="button.defaultRank"
domain="props.domain"
icon="button.icon"
string="button.string"
title="button.title"
className="button.className+' ms-2'"
/>
</t>
</t>
</t>
</xpath>
<xpath expr="//div/t[@t-if='nbSelected']" position="replace">
<t t-if="nbSelected">
<t t-foreach="archInfo.headerButtons" t-as="button" t-key="button.id">
<t t-if="!button.modifiers.force_show">
<ListViewHeaderButton
list="model.root"
clickParams="button.clickParams"
defaultRank="button.defaultRank"
domain="props.domain"
icon="button.icon"
string="button.string"
title="button.title"
/>
</t>
</t>
<t t-if="!env.isSmall">
<t t-call="web.ListView.Selection"/>
</t>
<t t-foreach="archInfo.headerButtons" t-as="button" t-key="button.id">
<t t-if="button.modifiers.force_show == 1">
<ListViewHeaderButton
list="model.root"
clickParams="button.clickParams"
defaultRank="button.defaultRank"
domain="props.domain"
icon="button.icon"
string="button.string"
title="button.title"
className="button.className+' ms-2'"
/>
</t>
</t>
</t>
</xpath>
</t>
</templates>

View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- agv站点 -->
<record id="view_agv_scheduling_tree" model="ir.ui.view">
<field name="name">agv调度</field>
<field name="model">sf.agv.scheduling</field>
<field name="arch" type="xml">
<tree editable="bottom" delete="0" create="0">
<field name="state" widget="badge"
decoration-success="state == '已配送'"
decoration-warning="state == '待下发'"
decoration-danger="state == '配送中'"
decoration-info="state == '已取消'"
/>
<field name="agv_route_type" invisible="1"/>
<field name="name"/>
<field name="agv_route_id"/>
<field name="start_site_id"/>
<field name="end_site_id"/>
<field name="site_state"/>
<field name="delivery_workpieces"/>
<field name="task_create_time" readonly="1"/>
<field name="task_delivery_time" readonly="1"/>
<field name="task_completion_time" readonly="1"/>
<field name="task_duration" readonly="1"/>
<button
name="button_cancel"
string="取消" type="object"
attrs="{'invisible': ['|', ('state', '!=', '待下发'), ('agv_route_type', '=', '运送空料架')]}"
icon="fa-times"
class="btn-danger"
confirm="你确定要取消这条记录吗?"
/>
</tree>
</field>
</record>
<record id="view_agv_scheduling_search" model="ir.ui.view">
<field name="name">sf.agv.scheduling.search</field>
<field name="model">sf.agv.scheduling</field>
<field name="arch" type="xml">
<search string="AGV调度">
<field name="name"/>
<field name="agv_route_id"/>
<field name="start_site_id"/>
<field name="end_site_id"/>
<field name="delivery_workpieces"/>
<field name="state" string="状态"/>
<filter name="filter_to_be_issued" string="待下发" domain="[('state', 'in', ['待下发'])]"/>
<filter name="filter_delivering" string="配送中" domain="[('state', 'in', ['配送中'])]"/>
<filter name="filter_delivered" string="已配送" domain="[('state', 'in', ['已配送'])]"/>
</search>
</field>
</record>
<record id="action_agv_scheduling_tree" model="ir.actions.act_window">
<field name="name">AGV调度</field>
<field name="res_model">sf.agv.scheduling</field>
<field name="view_mode">tree</field>
<field name="context">
{
"search_default_filter_to_be_issued": 1,
"search_default_filter_delivering": 1,
}
</field>
</record>
<menuitem
id="menu_action_agv_scheduling"
name="AGV调度"
sequence="28"
action="action_agv_scheduling_tree"
parent="mrp.menu_mrp_manufacturing"
groups="sf_base.group_sf_order_user,sf_base.group_sf_mrp_manager,sf_base.group_sf_equipment_user"
/>
</data>
</odoo>

View File

@@ -8,7 +8,7 @@
<field name="arch" type="xml">
<tree editable="bottom">
<field name="name" required="1" attrs="{'readonly': [('id', '!=', False)]}"/>
<field name="owning_region" required="1" attrs="{'readonly': [('id', '!=', False)]}"/>
<field name="workcenter_id" required="1" options="{'no_create': True}"/>
<field name="state" required="1" attrs="{'readonly': [('id', '!=', False)]}"/>
<field name="divide_the_work" required="1"/>
</tree>
@@ -40,8 +40,9 @@
<field name="start_site_id" required="1" options="{'no_create': True}" string="起点接驳站"
attrs="{'readonly': [('id', '!=', False)]}"/>
<field name="end_site_id" required="1" options="{'no_create': True}" string="终点接驳站"/>
<field name="destination_production_line_id" required="1" options="{'no_create': True}"
attrs="{'readonly': [('id', '!=', False)]}"/>
<!-- <field name="destination_production_line_id" required="1" options="{'no_create': True}"-->
<!-- attrs="{'readonly': [('id', '!=', False)]}"/>-->
<field name="workcenter_id"/>
</tree>
</field>
</record>

View File

@@ -7,10 +7,10 @@
<field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.mrp_production_tree_view"/>
<field name="arch" type="xml">
<!-- <xpath expr="//button[@name='do_unreserve']" position="after">-->
<!-- <button name="do_update_program" type="object" string="更新程序"-->
<!-- groups="sf_base.group_sf_mrp_user"/>-->
<!-- </xpath>-->
<!-- <xpath expr="//button[@name='do_unreserve']" position="after">-->
<!-- <button name="do_update_program" type="object" string="更新程序"-->
<!-- groups="sf_base.group_sf_mrp_user"/>-->
<!-- </xpath>-->
<xpath expr="//field[@name='product_id']" position="replace"/>
<xpath expr="//field[@name='product_qty']" position="replace"/>
<xpath expr="//field[@name='product_uom_id']" position="replace"/>
@@ -70,7 +70,7 @@
<!-- <attribute name="statusbar_visible">draft,confirmed,progress,pending_processing,completed,done -->
<!-- </attribute> -->
<attribute name="statusbar_visible">
confirmed,pending_cam,progress,done
confirmed,pending_cam,progress,rework,scrap,done
</attribute>
</xpath>
<xpath expr="//sheet//group//group[2]//label" position="before">
@@ -127,10 +127,10 @@
confirm="是否确认更新程序"
attrs="{'invisible': ['|',('state', '!=', 'rework'),('programming_state', '!=', '已编程未下发')]}"/>
<button name="button_rework" string="返工" type="object" groups="sf_base.group_sf_mrp_user"
attrs="{'invisible': ['|',('state', '!=', 'rework') ,('programming_state', '!=', '已编程')]}"/>
<!-- <button name="%(sf_manufacturing.action_sf_production_wizard)d" string="报废" type="action"-->
<!-- groups="sf_base.group_sf_mrp_user"-->
<!-- attrs="{'invisible': [('is_scrap', '=', False)]}"/>-->
attrs="{'invisible': ['|','|',('state', '!=', 'rework') ,('programming_state', '!=', '已编程'),('is_rework', '=', True)]}"/>
<button name="button_scrap_new" string="报废" type="object"
groups="sf_base.group_sf_mrp_user"
attrs="{'invisible': ['|',('is_scrap', '=', False),('state','=','cancel')]}"/>
</xpath>
<xpath expr="(//header//button[@name='button_mark_done'])[3]" position="replace">
<button name="button_mark_done" attrs="{'invisible': [
@@ -201,6 +201,19 @@
data-hotkey="l"/>
</xpath>
<xpath expr="//button[@name='action_view_mo_delivery']" position="before">
<button class="oe_stat_button" name="action_view_remanufacture_productions" type="object"
icon="fa-wrench" attrs="{'invisible': [('remanufacture_count', '=', 0)]}"
groups="mrp.group_mrp_user">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="remanufacture_count"/>
</span>
<span class="o_stat_text">新的制造</span>
</div>
</button>
</xpath>
<xpath expr="//header//button[@name='action_toggle_is_locked']" position="replace">
<button name="action_toggle_is_locked"
attrs="{'invisible': ['|', ('show_lock', '=', False), ('is_locked', '=', True)]}"
@@ -285,7 +298,7 @@
<xpath expr="//sheet//notebook//page[@name='operations']" position="after">
<page string="检测结果" attrs="{'invisible': [('detection_result_ids', '=', [])]}">
<field name="detection_result_ids" string="" readonly="1">
<field name="detection_result_ids" string="" readonly="0">
<tree sample="1">
<field name="production_id" invisible="1"/>
<field name="processing_panel"/>
@@ -424,6 +437,12 @@
<xpath expr="//header//button[@name='action_cancel']" position="replace">
<button name="action_cancel" type="object" string="取消" groups="sf_base.group_sf_mrp_user"/>
</xpath>
<xpath expr="//field[@name='state']" position="replace">
<field name="state" decoration-success="state in ('done', 'to_close')"
decoration-warning="state == 'progress'" decoration-info="state == 'confirmed'"
decoration-danger="state in ('cancel','rework','scrap')" decoration-muted="state == 'draft'"
optional="show" widget="badge" class="text-dark"/>
</xpath>
<xpath expr="//field[@name='state']" position="after">
<field name="tool_state" invisible="1"/>
</xpath>
@@ -460,6 +479,7 @@
<field name="arch" type="xml">
<xpath expr="//filter[@name='filter_in_progress']" position="before">
<filter string="返工" name="filter_rework" domain="[('state', '=', 'rework')]"/>
<filter string="报废" name="filter_scrap" domain="[('state', '=', 'scrap')]"/>
</xpath>
<xpath expr="//filter[@name='planning_issues']" position="before">
<separator/>
@@ -568,7 +588,7 @@
<field name="arch" type="xml">
<form>
<group>
<!-- <field name="handle_result"/>-->
<field name="handle_result"/>
<field name="test_report" readonly="1" widget="pdf_viewer"/>
</group>
</form>

View File

@@ -17,6 +17,7 @@
<field name="bom_product_template_attribute_value_ids" position="after">
<field name="routing_type" required="1"/>
<field name="is_repeat"/>
<field name="reserved_duration"/>
</field>
</field>
</record>

View File

@@ -182,6 +182,7 @@
</xpath>
<xpath expr="//field[@name='resource_calendar_id']" position="after">
<field name="is_process_outsourcing"/>
<field name="is_agv_scheduling"/>
</xpath>
</field>
</record>

View File

@@ -10,7 +10,7 @@
<field name="name" decoration-success="is_subcontract" decoration-bf="is_subcontract"/>
</field>
<field name="name" position="before">
<field name="sequence"/>
<field name="sequence" string="序号"/>
<field name='user_permissions' invisible="1"/>
</field>
<field name="name" position="after">
@@ -36,14 +36,22 @@
<xpath expr="//field[@name='date_planned_start']" position="replace">
<field name="date_planned_start" string="计划开始日期" optional="show"/>
</xpath>
<xpath expr="//field[@name='date_planned_start']" position="before">
<field name="reserved_duration" string="计划预留时间" optional="show"/>
</xpath>
<xpath expr="//field[@name='date_planned_finished']" position="replace">
<field name="date_planned_finished" string="计划结束日期" optional="hide"/>
</xpath>
<xpath expr="//button[@name='button_start']" position="attributes">
<!-- <attribute name="attrs">{'invisible': ['|', '|', '|','|','|', ('production_state','in', ('draft',-->
<!-- 'done',-->
<!-- 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel')),-->
<!-- ('is_user_working', '!=', False),("user_permissions","=",False),("name","=","CNC加工")]}-->
<!-- </attribute>-->
<attribute name="attrs">{'invisible': ['|', '|', '|','|','|', ('production_state','in', ('draft',
'done',
'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel')),
('is_user_working', '!=', False),("user_permissions","=",False),("name","=","CNC加工")]}
('is_user_working', '!=', False),("user_permissions","=",False),("name","in",("CNC加工","解除装夹"))]}
</attribute>
</xpath>
<xpath expr="//button[@name='%(mrp.act_mrp_block_workcenter_wo)d']" position="attributes">
@@ -113,11 +121,25 @@
<field name="model">mrp.workorder</field>
<field name="inherit_id" ref="mrp.mrp_production_workorder_form_view_inherit"/>
<field name="arch" type="xml">
<xpath expr="//form" position="inside">
<script src="sf_manufacturing/static/src/js/customRFID.js"/>
</xpath>
<xpath expr="//header/field[@name='state']" position="replace">
<field name="state" widget="statusbar"
statusbar_visible="pending,waiting,ready,progress,to be detected,done,rework"/>
</xpath>
<xpath expr="//div[@name='button_box']" position="inside">
<button type="object" name="action_view_surface_technics_purchase" class="oe_stat_button"
icon="fa-credit-card"
groups="base.group_user,sf_base.group_sf_order_user"
attrs="{'invisible': [('surface_technics_purchase_count', '=', 0),('routing_type', '!=', '表面工艺')]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="surface_technics_purchase_count"/>
</span>
<span class="o_stat_text">采购</span>
</div>
</button>
<button type="object" name="action_view_surface_technics_picking" class="oe_stat_button" icon="fa-truck"
groups="base.group_user,sf_base.group_sf_order_user"
attrs="{'invisible': [('surface_technics_picking_count', '=', 0)]}">
@@ -130,6 +152,7 @@
<field name='name' invisible="1"/>
<field name='is_rework' invisible="1"/>
<field name='is_delivery' invisible="1"/>
<field name="is_trayed" invisible="1"/>
<!-- <field name='is_send_program_again' invisible="1"/>-->
<!-- 工单form页面的开始停工按钮等 -->
<!-- <button name="button_start" type="object" string="开始" class="btn-success" -->
@@ -142,8 +165,12 @@
<!-- attrs="{'invisible': ['|', '|', ('production_state', 'not in', ('pending_processing', 'pending_cam', 'pending_era_cam')), ('state','!=','progress'), ('routing_type', 'not in', ('装夹预调', 'CNC加工', '解除装夹'))]}" -->
<!-- groups="sf_base.group_sf_mrp_user" confirm="是否确认完工"/> -->
<!-- <button name="button_start" type="object" string="开始" class="btn-success" confirm="是否确认开始"-->
<!-- attrs="{'invisible': ['|', '|', '|', ('production_state','in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel','to be detected')), ('is_user_working', '!=', False)]}"/>-->
<button name="button_start" type="object" string="开始" class="btn-success" confirm="是否确认开始"
attrs="{'invisible': ['|', '|', '|', ('production_state','in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel','to be detected')), ('is_user_working', '!=', False)]}"/>
attrs="{'invisible': ['|', '|', '|', '|', '|', ('routing_type', '=', '装夹预调'), ('routing_type', '=', '解除装夹'), ('production_state','in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel','to be detected')), ('is_user_working', '!=', False)]}"/>
<button name="button_start" type="object" string="开始" class="btn-success"
attrs="{'invisible': ['|', '|', '|', '|', ('routing_type', '!=', '装夹预调'), ('production_state','in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel','to be detected')), ('is_user_working', '!=', False)]}"/>
<button name="button_pending" type="object" string="暂停" class="btn-warning"
attrs="{'invisible': ['|', '|', ('production_state', 'in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('is_user_working', '=', False)]}"/>
<button name="button_finish" type="object" string="完成" class="btn-success" confirm="是否确认完工"
@@ -164,11 +191,14 @@
<!-- context="{'default_workcenter_id': workcenter_id}" class="btn-danger" -->
<!-- groups="sf_base.group_sf_mrp_user" -->
<!-- attrs="{'invisible': ['|', '|', ('production_state', 'in', ('draft', 'done', 'cancel')), ('working_state', '!=', 'blocked'),('state','=','done')]}"/> -->
<button name="button_workpiece_delivery" type="object" string="工件配送" class="btn-primary"
attrs="{'invisible': ['|','|','|','|',('routing_type','!=','装夹预调'),('is_delivery','=',True),('state','!=','done'),('is_rework','=',True),'&amp;',('rfid_code','in',['',False]),('state','=','done')]}"/>
<!-- <button name="button_workpiece_delivery" type="object" string="工件配送" class="btn-primary"-->
<!-- attrs="{'invisible': ['|','|','|','|',('routing_type','!=','装夹预调'),('is_delivery','=',True),('state','!=','done'),('is_rework','=',True),'&amp;',('rfid_code','in',['',False]),('state','=','done')]}"/>-->
<button name="button_rework_pre" type="object" string="返工"
class="btn-primary"
attrs="{'invisible': ['|','|',('routing_type','!=','装夹预调'),('state','!=','progress'),('is_rework','=',True)]}"/>
<button name="unbind_tray" type="object" string="解绑托盘"
class="btn-primary"
attrs="{'invisible': ['|', '|', '|', ('routing_type','!=','装夹预调'),('state','!=','progress'), ('is_trayed', '=', False), ('state', 'in', ('done'))]}"/>
<button name="print_method" type="object" string="打印二维码" class="btn-primary"
attrs="{'invisible': ['|',('routing_type','!=','解除装夹'),('state','!=','done')]}"/>
</xpath>
@@ -511,7 +541,7 @@
<xpath expr="//page[1]" position="before">
<page string="CNC程序" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'>
<field name="cnc_ids" widget="one2many" string="工作程序" default_order="sequence_number,id"
readonly="1">
readonly="0">
<tree>
<field name="sequence_number"/>
<field name="program_name"/>
@@ -631,27 +661,27 @@
<field name="arch" type="xml">
<tree string="工件配送" class="center" create="0" delete="0">
<header>
<button name="button_delivery" type="object" string="配送" class="oe_highlight"/>
<button name="button_delivery" type="object" string="工件配送" class="btn-primary" attrs="{'force_show':1}"/>
</header>
<field name="status" widget="badge"
decoration-success="status == '已配送'"
decoration-warning="status == '待下发'"
decoration-danger="status == '待配送'"
decoration-danger="status == '已下发'"
decoration-info="status == '已取消'"
/>
<field name="name"/>
<field name="production_id"/>
<field name="type" readonly="1"/>
<field name="production_line_id" options="{'no_create': True}" readonly="1"/>
<field name="route_id" options="{'no_create': True}"
domain="[('route_type','in',['上产线','下产线'])]"/>
<!-- <field name="route_id" options="{'no_create': True}"-->
<!-- domain="[('route_type','in',['上产线','下产线'])]"/>-->
<field name="feeder_station_start_id" readonly="1" force_save="1"/>
<field name="feeder_station_destination_id" readonly="1" force_save="1"/>
<!-- <field name="feeder_station_destination_id" readonly="1" force_save="1"/>-->
<field name="is_cnc_program_down" readonly="1"/>
<!-- <field name="rfid_code"/>-->
<field name="task_delivery_time" readonly="1"/>
<field name="task_completion_time" readonly="1"/>
<field name="delivery_duration" widget="float_time"/>
<!-- <field name="task_delivery_time" readonly="1"/>-->
<!-- <field name="task_completion_time" readonly="1"/>-->
<!-- <field name="delivery_duration" widget="float_time"/>-->
</tree>
</field>
</record>
@@ -706,7 +736,7 @@
<field name="arch" type="xml">
<search string="工件配送">
<filter name="filter_to_be_issued" string="待下发" domain="[('status', 'in', ['待下发'])]"/>
<filter name="filter_waiting_delivery" string="待配送" domain="[('status', 'in', ['待配送'])]"/>
<filter name="filter_issued" string="已下发" domain="[('status', 'in', ['已下发'])]"/>
<filter name="filter_delivered" string="已配送" domain="[('status', 'in', ['已配送'])]"/>
<field name="rfid_code"/>
<field name="production_id"/>
@@ -730,7 +760,7 @@
<field name="res_model">sf.workpiece.delivery</field>
<field name="search_view_id" ref="sf_workpiece_delivery_search"/>
<field name="context">{'search_default_filter_to_be_issued': 1,
'search_default_filter_waiting_delivery': 1}
'search_default_filter_issued': 1}
</field>
<field name="view_mode">tree,form</field>
<field name="domain">
@@ -817,5 +847,11 @@
<field name="view_mode">tree</field>
<field name="domain">[('type','in',['运送空料架']),('name','not ilike','WDO')]</field>
</record>
<menuitem id="mrp.menu_mrp_manufacturing"
name="Operations"
sequence="10"
parent="mrp.menu_mrp_root"
groups="sf_base.group_sf_order_user,sf_base.group_sf_mrp_manager,sf_base.group_sf_equipment_user"/>
</odoo>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="res_config_settings_view_form_sf_sync" model="ir.ui.view">
<field name="name">res.config.settings.view.form.inherit.sf_sync</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base_setup.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@id='agv_config']/div" position="after">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="is_agv_task_dispatch"/>
</div>
<div class="o_setting_right_pane">
<div class="text-muted">
<label for="is_agv_task_dispatch"/>
</div>
</div>
</div>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@@ -2,6 +2,8 @@
# Part of YiZuo. See LICENSE file for full copyright and licensing details.
import logging
from odoo.exceptions import UserError, ValidationError
from collections import defaultdict, namedtuple
from odoo.addons.stock.models.stock_rule import ProcurementException
from datetime import datetime
from odoo import models, api, fields, _
@@ -11,11 +13,91 @@ class ProductionWizard(models.TransientModel):
_description = '制造订单向导'
production_id = fields.Many2one('mrp.production', string='制造订单号')
is_reprogramming = fields.Boolean(string='申请重新编程', default=True)
reprogramming_num = fields.Integer('重新编程次数', default=0)
is_reprogramming = fields.Boolean(string='申请重新编程', default=False)
is_remanufacture = fields.Boolean(string='重新生成制造订单', default=True)
programming_states = fields.Selection(
[('待编程', '待编程'), ('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'),
('已下发', '已下发')],
string='编程状态')
@api.onchange('is_remanufacture')
def _onchange_is_reprogramming(self):
if self.is_remanufacture is False:
self.is_reprogramming = False
def confirm(self):
if self.is_reprogramming is True:
self.production_id.update_programming_state()
self.production_id.action_cancel()
self.production_id.detection_result_ids.write({'handle_result': '已处理'})
self.production_id.write({'state': 'cancel', 'scrap_ids': [(0, 0, {
'name': self.env['ir.sequence'].next_by_code('stock.scrap') or _('New'),
'product_id': self.production_id.product_id.id,
'scrap_qty': 1,
'origin': self.production_id.origin,
'date_done': fields.datetime.now(),
'lot_id': self.env['stock.move.line'].search(
[('move_id', '=', self.production_id.move_raw_ids[0].id)]).lot_id.id,
'location_id': self.production_id.move_raw_ids.filtered(lambda x: x.state not in (
'done',
'cancel')) and self.production_id.location_src_id.id or self.production_id.location_dest_id.id,
'scrap_location_id': self.env['stock.scrap']._get_default_scrap_location_id(),
'state': 'done'})]})
self.production_id.action_cancel()
if self.is_remanufacture is True:
ret = {'programming_list': [], 'is_reprogramming': self.is_reprogramming}
if self.is_reprogramming is True:
self.production_id.update_programming_state()
else:
scrap_cnc = self.production_id.workorder_ids.filtered(lambda crw: crw.routing_type == 'CNC加工').cnc_ids
scrap_cmm = self.production_id.workorder_ids.filtered(lambda cm: cm.routing_type == 'CNC加工').cmm_ids
for item_line in scrap_cnc:
vals = {
'sequence_number': item_line.sequence_number,
'program_name': item_line.program_name,
'cutting_tool_name': item_line.cutting_tool_name,
'cutting_tool_no': item_line.cutting_tool_no,
'processing_type': item_line.processing_type,
'margin_x_y': item_line.margin_x_y,
'margin_z': item_line.margin_z,
'depth_of_processing_z': item_line.depth_of_processing_z,
'cutting_tool_extension_length': item_line.cutting_tool_extension_length,
'estimated_processing_time': item_line.estimated_processing_time,
'cutting_tool_handle_type': item_line.cutting_tool_handle_type,
'ftp_path': item_line.program_path,
'processing_panel': item_line.workorder_id.processing_panel,
'program_create_date': datetime.strftime(item_line.program_create_date,
'%Y-%m-%d %H:%M:%S'),
'remark': item_line.remark
}
ret['programming_list'].append(vals)
for cmm_line in scrap_cmm:
vals = {
'sequence_number': cmm_line.sequence_number,
'program_name': cmm_line.program_name,
'ftp_path': cmm_line.program_path,
'processing_panel': item_line.workorder_id.processing_panel,
'program_create_date': datetime.strftime(
cmm_line.program_create_date,
'%Y-%m-%d %H:%M:%S')
}
ret['programming_list'].append(vals)
new_production = self.production_id.recreateManufacturing(ret)
self.production_id.write({'remanufacture_production_id': new_production.id})
if self.is_reprogramming is False:
for panel in new_production.product_id.model_processing_panel.split(','):
scrap_cnc_workorder = max(
self.production_id.workorder_ids.filtered(
lambda
scn: scn.processing_panel == panel and scn.routing_type == 'CNC加工'),
key=lambda w: w.create_date)
scrap_pre_workorder = max(self.production_id.workorder_ids.filtered(
lambda
pr: pr.processing_panel == panel and pr.routing_type == '装夹预调'),
key=lambda w1: w1.create_date)
new_cnc_workorder = new_production.workorder_ids.filtered(
lambda
nc: nc.processing_panel == panel and nc.routing_type == 'CNC加工')
new_cnc_workorder.write({'cnc_worksheet': scrap_cnc_workorder.cnc_worksheet})
new_pre_workorder = new_production.workorder_ids.filtered(lambda
p: p.routing_type == '装夹预调' and p.processing_panel == panel)
new_pre_workorder.write({'processing_drawing': scrap_pre_workorder.processing_drawing})

View File

@@ -6,14 +6,28 @@
<field name="arch" type="xml">
<form>
<sheet>
<field name="production_id" invisible="True"/>
<field name="production_id" invisible="1"/>
<field name="programming_states" invisible="1"/>
<div>
重新生成制造订单
<field name="is_remanufacture"/>
<field name="is_remanufacture" force_save="1"/>
</div>
<div>
申请重新编程
<field name="is_reprogramming" attrs='{"invisible": [("is_remanufacture","=",False)]}'/>
<div attrs='{"invisible": [("reprogramming_num","=",0)]}'>
注意: 该制造订单产品已申请重新编程次数为<field
name="reprogramming_num" string=""
readonly="1"
style='color:red;'/>,且当前编程状态为
<field name="programming_states" string=""
decoration-info="programming_states == '待编程'"
decoration-success="programming_states == '已下发'"
decoration-warning="programming_states =='编程中'"
decoration-danger="programming_states =='已编程'" readonly="1"/>
</div>
<div attrs='{"invisible": [("is_remanufacture","=",False)]}'>
<span style='font-weight:bold;'>申请重新编程
<field name="is_reprogramming" force_save="1"
attrs='{"readonly": [("programming_states","not in",["已下发"])]}'/>
</span>
</div>
<footer>
<button string="确认" name="confirm" type="object" class="oe_highlight" confirm="是否确认报废"/>
@@ -28,6 +42,9 @@
<field name="name">报废</field>
<field name="res_model">sf.production.wizard</field>
<field name="view_mode">form</field>
<!-- <field name="context">{-->
<!-- 'default_production_id': active_id}-->
<!-- </field>-->
<field name="target">new</field>
</record>

View File

@@ -71,9 +71,9 @@ class ReworkWizard(models.TransientModel):
lambda ap: ap.processing_panel == panel.name and ap.state != 'rework')
if panel_workorder:
panel_workorder.write({'state': 'rework'})
panel_workorder.filtered(
lambda wo: wo.routing_type == '装夹预调').workpiece_delivery_ids.filtered(
lambda wd: wd.status == '待下发').write({'status': '已取消'})
# panel_workorder.filtered(
# lambda wo: wo.routing_type == '装夹预调').workpiece_delivery_ids.filtered(
# lambda wd: wd.status == '待下发').write({'status': '已取消'})
# workpiece = self.env['sf.workpiece.delivery'].search([('status', '=', '待下发'), (
# 'workorder_id', '=',
# panel_workorder.filtered(lambda wd: wd.routing_type == '装夹预调').id)])

View File

@@ -16,10 +16,10 @@
attrs='{"invisible": [("routing_type","=","装夹预调")]}' widget="many2many_tags"/>
</group>
<div attrs='{"invisible": [("reprogramming_num","=",0)]}'>
注意: 该制造订单产品已重复编程过<field
注意: 该制造订单产品已申请重新编程次数为<field
name="reprogramming_num" string=""
readonly="1"
style='color:red;'/>,且当前编程状态为
style='color:red;'/>,且当前编程状态为
<field name="programming_state" string=""
decoration-info="programming_state == '待编程'"
decoration-success="programming_state == '已下发'"

View File

@@ -4,30 +4,22 @@
<field name="name">sf.workpiece.delivery.wizard.form.view</field>
<field name="model">sf.workpiece.delivery.wizard</field>
<field name="arch" type="xml">
<form>
<form js_class="remove_focus_view">
<sheet>
<field name="delivery_ids" invisible="True"/>
<field name="workorder_id" invisible="True"/>
<field name="type" invisible="True"/>
<group attrs="{'invisible': [('type', 'in', ['运送空料架'])]}">
<field name="workorder_ids" invisible="True"/>
<field name="delivery_type" invisible="True"/>
<field name="confirm_button" invisible="1"/>
<field name="_barcode_scanned" widget="barcode_handler"/>
<group col="1">
<field name="production_ids" readonly="1" widget="many2many_tags" string="制造订单号"/>
<div class="o_address_format">
<lable for="rfid_code"></lable>
<field name="rfid_code" class="o_address_zip"/>
<button name="recognize_production" string="识别" type="object" class="oe_highlight"/>
</div>
<field name="destination_production_line_id" readonly="1"/>
<field name="route_id"/>
<field name="delivery_type" readonly="1"/>
<field name="feeder_station_start_id" options="{'no_create': True}" required="1"/>
<field name="workcenter_id" options="{'no_create': True}"/>
</group>
<group attrs="{'invisible': [('type', 'in', ['运送空料架'])]}">
<field name="feeder_station_start_id" focesave="1" readonly="1"/>
<field name="feeder_station_destination_id" focesave="1" readonly="1"/>
</group>
<div attrs="{'invisible': [('type', 'in', ['上产线','下产线'])]}">
是否确定配送
</div>
<footer>
<button string="配送" name="confirm" type="object" class="oe_highlight"/>
<button string="确认配送" name="dispatch_confirm" type="object" class="oe_highlight o_wizard_confirm_button" attrs="{'invisible': [('confirm_button', '!=', '确认配送')]}"/>
<button string="确认解除" name="dispatch_confirm" type="object" class="oe_highlight o_wizard_confirm_button" attrs="{'invisible': [('confirm_button', '!=', '确认解除')]}"/>
<button string="取消" class="btn btn-secondary" special="cancel"/>
</footer>
</sheet>

View File

@@ -1,88 +1,208 @@
# -*- coding: utf-8 -*-
# Part of YiZuo. See LICENSE file for full copyright and licensing details.
import json
import logging
from odoo.exceptions import UserError, ValidationError
from datetime import datetime
from odoo import models, api, fields, _
from datetime import datetime, date
from odoo import models, api, fields
def convert_datetime(obj):
if isinstance(obj, (datetime, date)):
return obj.isoformat() # 将 datetime 或 date 对象转换为 ISO 8601 字符串格式
raise TypeError(f"Type {type(obj)} not serializable")
class WorkpieceDeliveryWizard(models.TransientModel):
_name = 'sf.workpiece.delivery.wizard'
_inherit = ["barcodes.barcode_events_mixin"]
_description = '工件配送'
delivery_ids = fields.Many2many('sf.workpiece.delivery', string='配送')
rfid_code = fields.Char('rfid码')
workorder_id = fields.Many2one('mrp.workorder', string='')
delivery_ids = fields.Many2many('sf.workpiece.delivery', string='配送')
workorder_ids = fields.Many2many('mrp.workorder', string='工单')
production_ids = fields.Many2many('mrp.production', string='制造订单号')
destination_production_line_id = fields.Many2one('sf.production.line', '目的生产线')
route_id = fields.Many2one('sf.agv.task.route', '任务路线', domain=[('route_type', 'in', ['上产线', '下产线'])])
feeder_station_start_id = fields.Many2one('sf.agv.site', '起点接驳站')
feeder_station_destination_id = fields.Many2one('sf.agv.site', '目的接驳站')
type = fields.Selection(
[('上产线', '上产线'), ('下产线', '下产线'), ('运送空料架', '运送空料架')], string='类型')
workcenter_id = fields.Many2one(string='所属区域', comodel_name='mrp.workcenter', tracking=True)
confirm_button = fields.Char('按钮名称')
@api.onchange('delivery_type')
def _onchange_type(self):
if self.delivery_type:
routes = self.env['sf.agv.task.route'].search([('route_type', '=', self.delivery_type)])
if self.workcenter_id:
routes = routes.filtered(lambda a: a.start_site_id.workcenter_id.id == self.workcenter_id.id)
start_site_ids = routes.mapped('start_site_id.id')
workcenter_ids = routes.mapped('end_site_id.workcenter_id.id')
if workcenter_ids:
self.workcenter_id = workcenter_ids[0]
return {
'domain':
{
'feeder_station_start_id': [('id', 'in', start_site_ids)],
'workcenter_id': [('id', 'in', workcenter_ids)],
}
}
else:
return {
'domain':
{
'feeder_station_start_id': [],
'workcenter_id': [],
}
}
def _get_agv_route_type_selection(self):
return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection']
delivery_type = fields.Selection(selection=_get_agv_route_type_selection, string='类型')
def dispatch_confirm(self):
if len(self.workorder_ids) < 4:
return {
'type': 'ir.actions.client',
'tag': 'dispatch_confirm',
'params': {
'workorder_count': len(self.workorder_ids),
'active_id': self.id,
'context': self.env.context
}
}
else:
scheduling = self.confirm()
# 显示通知
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'target': 'new',
'params': {
'message': '任务下发成功AGV任务调度编号为【%s' % scheduling['name'],
'type': 'success',
'sticky': False,
'next': {'type': 'ir.actions.act_window_close'},
}
}
def confirm(self):
if self.type != '运送空料架':
if not self.route_id:
raise UserError('请选择路线')
if self.workorder_id:
self.workorder_id.workpiece_delivery_ids[0]._delivery_avg()
else:
is_not_production_line = 0
same_production_line_id = None
notsame_production_line_arr = []
for item in self.production_ids:
if same_production_line_id is None:
same_production_line_id = item.production_line_id.id
if item.production_line_id.id != same_production_line_id:
notsame_production_line_arr.append(item.name)
notsame_production_line_str = ','.join(map(str, notsame_production_line_arr))
if is_not_production_line >= 1:
raise UserError('制造订单号为%s的目的生产线不一致' % notsame_production_line_str)
else:
self.delivery_ids._delivery_avg()
try:
# if self.workorder_id:
# self.workorder_id.workpiece_delivery_ids[0].agv_scheduling_id()
# else:
# is_not_production_line = 0
# same_production_line_id = None
# notsame_production_line_arr = []
# for item in self.production_ids:
# if same_production_line_id is None:
# same_production_line_id = item.production_line_id.id
# if item.production_line_id.id != same_production_line_id:
# notsame_production_line_arr.append(item.name)
# notsame_production_line_str = ','.join(map(str, notsame_production_line_arr))
# if is_not_production_line >= 1:
# raise UserError('制造订单号为%s的目的生产线不一致' % notsame_production_line_str)
# else:
scheduling = self.env['sf.agv.scheduling'].add_scheduling(
agv_start_site_name=self.feeder_station_start_id.name,
agv_route_type=self.delivery_type,
workorders=self.workorder_ids,
)
# 如果关联了工件配送单,则修改状态为已下发
if self.delivery_ids:
val = {
'status': '已下发',
'agv_scheduling_id': scheduling.id,
'feeder_station_start_id': scheduling.start_site_id.id,
}
# 如果agv任务已经下发则修改工件配送单信息
if scheduling.state == '配送中':
val.update({
'feeder_station_destination_id': scheduling.end_site_id.id,
'route_id': scheduling.agv_route_id.id,
'task_delivery_time': fields.Datetime.now()
})
self.delivery_ids.write(val)
def recognize_production(self):
# production_ids = []
# delivery_ids = []
# aa = self.production_ids.workorder_ids.filtered(
# lambda b: b.routing_type == "装夹预调").workpiece_delivery_ids.filtered(
# lambda c: c.rfid_code == self.rfid_code)
# logging.info('aa:%s' % aa)
if len(self.production_ids) == 4:
raise UserError('只能配送四个制造订单')
else:
if self.rfid_code:
wd = self.env['sf.workpiece.delivery'].search(
[('type', '=', self.delivery_ids[0].type), ('rfid_code', '=', self.rfid_code),
('status', '=', self.delivery_ids[0].status)])
if wd:
if wd.production_line_id.id == self.delivery_ids[0].production_line_id.id:
# production_ids.append(wd.production_id)
# delivery_ids.append(wd.id)
# 将对象添加到对应的同模型且是多对多类型里
self.production_ids |= wd.production_id
self.delivery_ids |= wd
self.rfid_code = False
# self.production_ids = [(6, 0, production_ids)]
# self.delivery_ids = [(6, 0, delivery_ids)]
else:
raise UserError('该rfid对应的制造订单号为%s的目的生产线不一致' % wd.production_id.name)
return {
'name': _('确认'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'sf.workpiece.delivery.wizard',
'target': 'new',
'context': {
'default_delivery_ids': [(6, 0, self.delivery_ids.ids)],
'default_production_ids': [(6, 0, self.production_ids.ids)],
'default_route_id': self.delivery_ids[0].route_id.id,
'default_type': self.delivery_ids[0].type
}}
# 如果是解除装夹工单,则需要处理工单逻辑
for item in self.workorder_ids:
if item.routing_type == '解除装夹' and item.state == 'ready':
item.button_start()
item.button_finish()
return scheduling.read()[0]
except Exception as e:
logging.info('%s任务下发失败:%s' % (self.delivery_type, e))
raise UserError('%s任务下发失败:%s' % (self.delivery_type, e))
# def recognize_production(self):
# # production_ids = []
# # delivery_ids = []
# # aa = self.production_ids.workorder_ids.filtered(
# # lambda b: b.routing_type == "装夹预调").workpiece_delivery_ids.filtered(
# # lambda c: c.rfid_code == self.rfid_code)
# # logging.info('aa:%s' % aa)
# if len(self.production_ids) == 4:
# raise UserError('只能配送四个制造订单')
# else:
# if self.rfid_code:
# wd = self.env['sf.workpiece.delivery'].search(
# [('type', '=', self.delivery_ids[0].type), ('rfid_code', '=', self.rfid_code),
# ('status', '=', self.delivery_ids[0].status)])
# if wd:
# if wd.production_line_id.id == self.delivery_ids[0].production_line_id.id:
# # production_ids.append(wd.production_id)
# # delivery_ids.append(wd.id)
# # 将对象添加到对应的同模型且是多对多类型里
# self.production_ids |= wd.production_id
# self.delivery_ids |= wd
# self.rfid_code = False
# # self.production_ids = [(6, 0, production_ids)]
# # self.delivery_ids = [(6, 0, delivery_ids)]
# else:
# raise UserError('该rfid对应的制造订单号为%s的目的生产线不一致' % wd.production_id.name)
# return {
# 'name': _('确认'),
# 'type': 'ir.actions.act_window',
# 'view_mode': 'form',
# 'res_model': 'sf.workpiece.delivery.wizard',
# 'target': 'new',
# 'context': {
# 'default_delivery_ids': [(6, 0, self.delivery_ids.ids)],
# 'default_production_ids': [(6, 0, self.production_ids.ids)],
# 'default_route_id': self.delivery_ids[0].route_id.id,
# 'default_type': self.delivery_ids[0].type
# }}
@api.onchange('route_id')
def onchange_route(self):
if self.route_id:
self.feeder_station_start_id = self.route_id.start_site_id.id
self.feeder_station_destination_id = self.route_id.end_site_id.id
def on_barcode_scanned(self, barcode):
delivery_type = self.env.context.get('default_delivery_type')
if delivery_type == '上产线':
workorder = self.env['mrp.workorder'].search(
[('production_line_state', '=', '待上产线'), ('rfid_code', '=', barcode),
('state', '=', 'done')])
# 找到对应的配送单
delivery = self.env['sf.workpiece.delivery'].search(
[('type', '=', '上产线'), ('rfid_code', '=', barcode),
('status', '=', '待下发')])
if delivery:
self.delivery_ids |= delivery
elif delivery_type == '运送空料架':
workorder = self.env['mrp.workorder'].search(
[('routing_type', '=', '解除装夹'), ('rfid_code', '=', barcode),
('state', '=', 'ready')])
if workorder:
if (len(self.production_ids) > 0 and
workorder.production_line_id.id != self.production_ids[0].production_line_id.id):
raise UserError('该rfid对应的制造订单号为%s的目的生产线不一致' % workorder.production_id.name)
# 将对象添加到对应的同模型且是多对多类型里
self.production_ids |= workorder.production_id
self.workorder_ids |= workorder
else:
raise UserError('该rfid码对应的工单不存在')
return

File diff suppressed because it is too large Load Diff

View File

@@ -24,11 +24,13 @@ class Sf_Mrs_Connect(http.Controller):
ret = json.loads(datas)
ret = json.loads(ret['result'])
logging.info('下发编程单:%s' % ret)
domain = [('programming_no', '=', ret['programming_no'])]
if ret['manufacturing_type'] == 'scrap':
domain += [('state', 'not in', ['done', 'scrap', 'cancel'])]
productions = request.env['mrp.production'].with_user(
request.env.ref("base.user_admin")).search(
[('programming_no', '=', ret['programming_no'])])
request.env.ref("base.user_admin")).search(domain)
if productions:
# # 拉取所有加工面的程序文件
# 拉取所有加工面的程序文件
for r in ret['processing_panel'].split(','):
program_path_tmp_r = os.path.join('/tmp', ret['folder_name'], 'return', r)
if os.path.exists(program_path_tmp_r):
@@ -48,45 +50,28 @@ class Sf_Mrs_Connect(http.Controller):
if not production.workorder_ids:
production.product_id.model_processing_panel = ret['processing_panel']
production._create_workorder(ret)
# else:
# for panel in ret['processing_panel'].split(','):
# # 查询状态为进行中且工序类型为CNC加工的工单
# cnc_workorder = production.workorder_ids.filtered(
# lambda ac: ac.routing_type == 'CNC加工' and ac.state not in ['progress', 'done',
# 'cancel'] and ac.processing_panel == panel)
# if cnc_workorder:
# if cnc_workorder.cnc_ids:
# cnc_workorder.cmm_ids.sudo().unlink()
# cnc_workorder.cnc_ids.sudo().unlink()
# request.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan(
# production)
# # program_path_tmp_panel = os.path.join('C://Users//43484//Desktop//fsdownload//test',
# # panel)
# program_path_tmp_panel = os.path.join('/tmp', ret['folder_name'], 'return', panel)
# logging.info('program_path_tmp_panel:%s' % program_path_tmp_panel)
# files_panel = os.listdir(program_path_tmp_panel)
# if files_panel:
# for file in files_panel:
# file_extension = os.path.splitext(file)[1]
# logging.info('file_extension:%s' % file_extension)
# if file_extension.lower() == '.pdf':
# panel_file_path = os.path.join(program_path_tmp_panel, file)
# logging.info('panel_file_path:%s' % panel_file_path)
# cnc_workorder.write(
# {'cnc_ids': cnc_workorder.cnc_ids.sudo()._json_cnc_processing(panel, ret),
# 'cmm_ids': cnc_workorder.cmm_ids.sudo()._json_cmm_program(panel, ret),
# 'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
# pre_workorder = production.workorder_ids.filtered(
# lambda ap: ap.routing_type == '装夹预调' and ap.state not in ['done',
# 'cancel'] and ap.processing_panel == panel)
# if pre_workorder:
# pre_workorder.write(
# {'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())})
productions.process_range_time()
else:
for panel in ret['processing_panel'].split(','):
# 查询状态为进行中且工序类型为CNC加工的工单
cnc_workorder_has = production.workorder_ids.filtered(
lambda ach: ach.routing_type == 'CNC加工' and ach.state not in ['progress', 'done',
'rework',
'cancel'] and ach.processing_panel == panel)
if cnc_workorder_has:
if cnc_workorder_has.cnc_ids:
cnc_workorder_has.cmm_ids.sudo().unlink()
cnc_workorder_has.cnc_ids.sudo().unlink()
request.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan(
production)
cnc_workorder_has.write(
{'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)})
for panel in ret['processing_panel'].split(','):
# 查询状态为进行中且工序类型为CNC加工的工单
cnc_workorder = productions.workorder_ids.filtered(
lambda ac: ac.routing_type == 'CNC加工' and ac.state not in ['progress', 'done',
'cancel'] and ac.processing_panel == panel)
lambda ac: ac.routing_type == 'CNC加工' and ac.state not in ['progress', 'done', 'rework'
'cancel'] and ac.processing_panel == panel)
if cnc_workorder:
# program_path_tmp_panel = os.path.join('C://Users//43484//Desktop//fsdownload//test',
# panel)
@@ -102,18 +87,12 @@ class Sf_Mrs_Connect(http.Controller):
logging.info('panel_file_path:%s' % panel_file_path)
cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
pre_workorder = productions.workorder_ids.filtered(
lambda ap: ap.routing_type == '装夹预调' and ap.state not in ['done',
'cancel'] and ap.processing_panel == panel)
lambda ap: ap.routing_type == '装夹预调' and ap.state not in ['done', 'rework'
'cancel'] and ap.processing_panel == panel)
if pre_workorder:
pre_workorder.write(
{'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())})
productions.write({'programming_state': '已编程', 'work_state': '已编程'})
cnc_program_ids = [item.id for item in productions]
workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search(
[('production_id', 'in', cnc_program_ids)])
if workpiece_delivery:
workpiece_delivery.write(
{'is_cnc_program_down': True, 'production_line_id': productions.production_line_id.id})
return json.JSONEncoder().encode(res)
else:
res = {'status': 0, 'message': '该制造订单暂未开始'}

View File

@@ -329,6 +329,7 @@ class sfProductionProcess(models.Model):
production_process.processing_day = item['processing_day']
production_process.travel_day = item['travel_day']
production_process.active = item['active']
production_process.sequence = item['sequence']
else:
self.create({
"name": item['name'],
@@ -338,6 +339,7 @@ class sfProductionProcess(models.Model):
"processing_day": item['processing_day'],
"travel_day": item['travel_day'],
"active": item['active'],
"sequence": item['sequence']
})
else:
raise ValidationError("表面工艺认证未通过")
@@ -365,6 +367,7 @@ class sfProductionProcess(models.Model):
"processing_day": item['processing_day'],
"travel_day": item['travel_day'],
"active": item['active'],
"sequence": item['sequence']
})
else:
production_process.name = item['name']
@@ -373,6 +376,7 @@ class sfProductionProcess(models.Model):
production_process.processing_day = item['processing_day']
production_process.travel_day = item['travel_day']
production_process.active = item['active']
production_process.sequence = item['sequence']
else:
raise ValidationError("表面工艺认证未通过")
@@ -1088,6 +1092,7 @@ class sfProductionProcessParameter(models.Model):
production_process_parameter.process_id = process.id
production_process_parameter.materials_model_ids = self.env['sf.materials.model'].search(
[('materials_no', 'in', item['materials_model_ids_codes'])])
production_process_parameter.processing_mm = item['processing_mm']
else:
self.create({
"name": item['name'],
@@ -1099,6 +1104,7 @@ class sfProductionProcessParameter(models.Model):
"process_id": process.id,
"materials_model_ids": self.env['sf.materials.model'].search(
[('materials_no', 'in', item['materials_model_ids_codes'])]),
"processing_mm": item['processing_mm']
})
else:
raise ValidationError("表面工艺可选参数认证未通过") # 定时同步表面工艺
@@ -1129,6 +1135,7 @@ class sfProductionProcessParameter(models.Model):
"process_id": process.id,
'materials_model_ids': self.env['sf.materials.model'].search(
[('materials_no', 'in', item['materials_model_ids_codes'])]),
'processing_mm': item['processing_mm']
})
else:
production_process_parameter.name = item['name']
@@ -1139,6 +1146,7 @@ class sfProductionProcessParameter(models.Model):
production_process_parameter.materials_model_ids = self.env['sf.materials.model'].search(
[('materials_no', 'in', item['materials_model_ids_codes'])])
production_process_parameter.active = item['active']
production_process_parameter.processing_mm = item['processing_mm']
else:
raise ValidationError("表面工艺可选参数认证未通过")

View File

@@ -76,7 +76,7 @@
</div>
<div>
<h2>AGV参数配置</h2>
<div class="row mt16 o_settings_container">
<div class="row mt16 o_settings_container" id="agv_config">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">

View File

@@ -337,6 +337,7 @@
name="空料架配送"
sequence="11"
action="sf_manufacturing.sf_workpiece_delivery_empty_racks_act"
groups="base.group_system"
parent="mrp.menu_mrp_manufacturing"
/>
<!-- <menuitem -->

View File

@@ -16,6 +16,8 @@ class Action_Plan_All_Wizard(models.TransientModel):
# 选择生产线
production_line_id = fields.Many2one('sf.production.line', string=u'生产线', required=True)
date_planned_start = fields.Datetime(string='计划开始时间', index=True, copy=False,
default=fields.Datetime.now)
# 接收传递过来的计划ID
plan_ids = fields.Many2many('sf.production.plan', string=u'计划ID')
@@ -33,6 +35,7 @@ class Action_Plan_All_Wizard(models.TransientModel):
# 拿到计划对象
plan_obj = self.env['sf.production.plan'].browse(plan.id)
plan_obj.production_line_id = self.production_line_id.id
plan.date_planned_start = self.date_planned_start
plan_obj.do_production_schedule()
# plan_obj.state = 'done'
print('处理计划:', plan.id, '完成')

View File

@@ -7,6 +7,7 @@
<form>
<group>
<field name="production_line_id" domain="[('name', 'ilike', 'CNC')]"/>
<field name="date_planned_start"/>
</group>
<footer>
<button string="确认排程" name="action_plan_all" type="object" class="btn-primary"/>

View File

@@ -48565,3 +48565,16 @@ msgstr ""
#: model:ir.model.fields.selection,name:sf_maintenance.selection__maintenance_equipment__heightened_way__chilunjia
msgid "齿轮架驱动"
msgstr ""
#. module: sf_manufacturing
#. odoo-python
#: code:addons/sf_manufacturing/models/mrp_production.py:0
#: model:ir.actions.act_window,name:sf_manufacturing.action_sf_production_wizard
#: model:ir.model.fields.selection,name:sf_manufacturing.selection__mrp_production__state__scrap
#: model:ir.model.fields.selection,name:sf_manufacturing.selection__mrp_workorder__test_results__报废
#: model:ir.model.fields.selection,name:sf_manufacturing.selection__sf_detection_result__test_results__报废
#: model_terms:ir.ui.view,arch_db:sf_manufacturing.custom_mrp_production_form_view
#: model_terms:ir.ui.view,arch_db:sf_manufacturing.custom_view_mrp_production_filter
#, python-format
msgid "报废"
msgstr "报废"

View File

@@ -20,7 +20,8 @@
'views/sale_order_view.xml',
'views/res_partner_view.xml',
'views/purchase_order_view.xml',
'views/quick_easy_order_view.xml'
'views/quick_easy_order_view.xml',
'views/purchase_menu.xml'
],
'assets': {
'web.assets_backend': [

View File

@@ -371,6 +371,7 @@ class QuickEasyOrder(models.Model):
product_bom_purchase.bom_create_line_has(purchase_embryo)
order_id.with_user(self.env.ref("base.user_admin")).sale_order_create_line(product, item)
except Exception as e:
logging.error('工厂创建销售订单和产品失败,请联系管理员'.format(e))
# self.cr.rollback()
return UserError('工厂创建销售订单和产品失败,请联系管理员')

View File

@@ -13,6 +13,11 @@ READONLY_FIELD_STATES = {
class ReSaleOrder(models.Model):
_inherit = 'sale.order'
mrp_production_count = fields.Integer(
"Count of MO generated",
compute='_compute_mrp_production_ids',
groups='mrp.group_mrp_user,sf_base.group_sale_salemanager,sf_base.group_sale_director')
logistics_way = fields.Selection([('自提', '自提'), ('到付', '到付'), ('在线支付', '在线支付')], string='物流方式')
state = fields.Selection(
selection=[
@@ -55,7 +60,7 @@ class ReSaleOrder(models.Model):
deadline_of_delivery, payments_way, pay_way):
now_time = datetime.datetime.now()
partner = self.get_customer()
order_id = self.env['sale.order'].sudo().create({
data = {
'company_id': company_id.id,
'date_order': now_time,
'name': self.env['ir.sequence'].next_by_code('sale.order', sequence_date=now_time),
@@ -66,10 +71,18 @@ class ReSaleOrder(models.Model):
'person_of_delivery': delivery_name,
'telephone_of_delivery': delivery_telephone,
'address_of_delivery': delivery_address,
'deadline_of_delivery': deadline_of_delivery,
'payments_way': payments_way,
'pay_way': pay_way,
})
}
if deadline_of_delivery:
# deadline_of_delivery字段存在为false字符串情况
if not isinstance(deadline_of_delivery, str):
data.update({'deadline_of_delivery': deadline_of_delivery})
else:
if deadline_of_delivery != "False":
data.update({'deadline_of_delivery': deadline_of_delivery})
order_id = self.env['sale.order'].sudo().create(data)
return order_id
def write(self, vals):
@@ -211,6 +224,44 @@ class RePurchaseOrder(models.Model):
if not line.taxes_id:
raise UserError('请对【产品】中的【税】进行选择')
def get_purchase_order(self, consecutive_process_parameters, production, product_id_to_production_names):
server_product_process = []
production_process = product_id_to_production_names.get(
production.product_id.id)
for pp in consecutive_process_parameters:
if pp.gain_way == '外协':
server_template = self.env['product.template'].search(
[('server_product_process_parameters_id', '=', pp.id),
('detailed_type', '=', 'service')])
purchase_order_line = self.env['purchase.order.line'].search(
[('product_id', '=', server_template.product_variant_id.id),
('product_qty', '=', len(production_process))], limit=1, order='id desc')
if not purchase_order_line:
server_product_process.append((0, 0, {
'product_id': server_template.product_variant_id.id,
'product_qty': len(production_process),
'product_uom': server_template.uom_id.id
}))
else:
for item in purchase_order_line:
if production.name in production_process:
purchase_order = self.env['purchase.order'].search(
[('state', '=', 'draft'), ('origin', '=', ','.join(production_process)),
('id', '=', item.order_id.id)])
if not purchase_order:
server_product_process.append((0, 0, {
'product_id': server_template.product_variant_id.id,
'product_qty': len(production_process),
'product_uom': server_template.uom_id.id
}))
if server_product_process:
self.env['purchase.order'].sudo().create({
'partner_id': server_template.seller_ids.partner_id.id,
'origin': ','.join(production_process),
'state': 'draft',
'order_line': server_product_process})
# self.env.cr.commit()
@api.onchange('order_line')
def _onchange_order_line(self):
for order in self:
@@ -230,6 +281,14 @@ class RePurchaseOrder(models.Model):
if picking_id.move_ids:
for move_id in picking_id.move_ids:
move_id.put_move_line()
for line in item.order_line:
if line.product_id.categ_type == '表面工艺':
for production_name in item.origin.split(','):
production = self.env['mrp.production'].search([('name', '=', production_name)])
for workorder in production.workorder_ids.filtered(
lambda wd: wd.routing_type == '表面工艺' and wd.state == 'waiting' and line.product_id.server_product_process_parameters_id == wd.surface_technics_parameters_id):
workorder.state = 'ready'
return result

View File

@@ -96,5 +96,28 @@ access_product_supplierinfo_group_plan_director,product.supplierinfo user,produc
access_product_category_group_plan_director,product.category user,product.model_product_category,sf_base.group_plan_director,1,1,1,0
access_purchase_report_sf_base_group_purchase,purchase_report_sf_base_group_purchase,purchase.model_purchase_report,sf_base.group_purchase,1,0,0,0
access_purchase_report_sf_base_group_purchase_director,purchase_report_sf_base_group_purchase_director,purchase.model_purchase_report,sf_base.group_purchase_director,1,0,0,0
access_sale_order_sf_base_group_purchase,sale_order_sf_base_group_purchase,model_sale_order,sf_base.group_purchase,1,0,0,0
access_sale_order_sf_base_group_purchase_director,sale_order_sf_base_group_purchase_director,model_sale_order,sf_base.group_purchase_director,1,0,0,0
access_quality_check_group_sale_salemanager,quality_check_group_sale_salemanager,quality.model_quality_check,sf_base.group_sale_salemanager,1,0,0,0
access_quality_check_group_sale_director,quality_check_group_sale_director,quality.model_quality_check,sf_base.group_sale_director,1,0,0,0
access_stock_picking_group_sale_salemanager,stock_picking_group_sale_salemanager,stock.model_stock_picking,sf_base.group_sale_salemanager,1,0,0,0
access_stock_picking_group_sale_director,stock_picking_group_sale_director,stock.model_stock_picking,sf_base.group_sale_director,1,0,0,0
access_mrp_workorder_group_sale_salemanager,mrp_workorder_group_sale_salemanager,mrp.model_mrp_workorder,sf_base.group_sale_salemanager,1,0,0,0
access_mrp_workorder_group_sale_director,mrp_workorder_group_sale_director,mrp.model_mrp_workorder,sf_base.group_sale_director,1,0,0,0
access_mrp_unbuild_group_sale_salemanager,mrp_unbuild_group_sale_salemanager,mrp.model_mrp_unbuild,sf_base.group_sale_salemanager,1,0,0,0
access_mrp_unbuild_group_sale_director,mrp_unbuild_group_sale_director,mrp.model_mrp_unbuild,sf_base.group_sale_director,1,0,0,0
access_mrp_workcenter_productivity_group_sale_salemanager,mrp_workcenter_productivity_group_sale_salemanager,mrp.model_mrp_workcenter_productivity,sf_base.group_sale_salemanager,1,0,0,0
access_mrp_workcenter_productivity_group_sale_director,mrp_workcenter_productivity_group_sale_director,mrp.model_mrp_workcenter_productivity,sf_base.group_sale_director,1,0,0,0
access_sf_detection_result_group_sale_salemanager,sf_detection_result_group_sale_salemanager,sf_manufacturing.model_sf_detection_result,sf_base.group_sale_salemanager,1,0,0,0
access_sf_detection_result_group_sale_director,sf_detection_result_group_sale_director,sf_manufacturing.model_sf_detection_result,sf_base.group_sale_director,1,0,0,0
access_stock_scrap_group_sale_salemanager,stock_scrap_group_sale_salemanager,stock.model_stock_scrap,sf_base.group_sale_salemanager,1,0,0,0
access_stock_scrap_group_sale_director,stock_scrap_group_sale_director,stock.model_stock_scrap,sf_base.group_sale_director,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
96 access_purchase_report_sf_base_group_purchase_director purchase_report_sf_base_group_purchase_director purchase.model_purchase_report sf_base.group_purchase_director 1 0 0 0
97 access_sale_order_sf_base_group_purchase sale_order_sf_base_group_purchase model_sale_order sf_base.group_purchase 1 0 0 0
98 access_sale_order_sf_base_group_purchase_director sale_order_sf_base_group_purchase_director model_sale_order sf_base.group_purchase_director 1 0 0 0
99 access_quality_check_group_sale_salemanager quality_check_group_sale_salemanager quality.model_quality_check sf_base.group_sale_salemanager 1 0 0 0
100 access_quality_check_group_sale_director quality_check_group_sale_director quality.model_quality_check sf_base.group_sale_director 1 0 0 0
101 access_stock_picking_group_sale_salemanager stock_picking_group_sale_salemanager stock.model_stock_picking sf_base.group_sale_salemanager 1 0 0 0
102 access_stock_picking_group_sale_director stock_picking_group_sale_director stock.model_stock_picking sf_base.group_sale_director 1 0 0 0
103 access_mrp_workorder_group_sale_salemanager mrp_workorder_group_sale_salemanager mrp.model_mrp_workorder sf_base.group_sale_salemanager 1 0 0 0
104 access_mrp_workorder_group_sale_director mrp_workorder_group_sale_director mrp.model_mrp_workorder sf_base.group_sale_director 1 0 0 0
105 access_mrp_unbuild_group_sale_salemanager mrp_unbuild_group_sale_salemanager mrp.model_mrp_unbuild sf_base.group_sale_salemanager 1 0 0 0
106 access_mrp_unbuild_group_sale_director mrp_unbuild_group_sale_director mrp.model_mrp_unbuild sf_base.group_sale_director 1 0 0 0
107 access_mrp_workcenter_productivity_group_sale_salemanager mrp_workcenter_productivity_group_sale_salemanager mrp.model_mrp_workcenter_productivity sf_base.group_sale_salemanager 1 0 0 0
108 access_mrp_workcenter_productivity_group_sale_director mrp_workcenter_productivity_group_sale_director mrp.model_mrp_workcenter_productivity sf_base.group_sale_director 1 0 0 0
109 access_sf_detection_result_group_sale_salemanager sf_detection_result_group_sale_salemanager sf_manufacturing.model_sf_detection_result sf_base.group_sale_salemanager 1 0 0 0
110 access_sf_detection_result_group_sale_director sf_detection_result_group_sale_director sf_manufacturing.model_sf_detection_result sf_base.group_sale_director 1 0 0 0
111 access_stock_scrap_group_sale_salemanager stock_scrap_group_sale_salemanager stock.model_stock_scrap sf_base.group_sale_salemanager 1 0 0 0
112 access_stock_scrap_group_sale_director stock_scrap_group_sale_director stock.model_stock_scrap sf_base.group_sale_director 1 0 0 0
113
114
115
116
117
118
119
120
121
122
123

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<!-- 采购-产品 -->
<menuitem id="purchase.menu_purchase_products" name="Products" parent="purchase.menu_purchase_root"
groups="sf_base.group_purchase_director,sf_base.group_purchase"
sequence="5"/>
<!-- 采购-产品-产品 -->
<menuitem id="purchase.menu_procurement_partner_contact_form" name="Products"
action="purchase.product_normal_action_puchased" parent="purchase.menu_purchase_products"
groups="sf_base.group_purchase_director,sf_base.group_purchase"
sequence="20"/>
<!-- 采购-报表 -->
<menuitem id="purchase.purchase_report_main" name="Reporting" parent="purchase.menu_purchase_root" sequence="99"
groups="purchase.group_purchase_manager,sf_base.group_purchase_director,sf_base.group_purchase"/>
<!-- 采购-报表-采购 -->
<menuitem id="purchase.purchase_report" name="Purchase" parent="purchase.purchase_report_main" sequence="99"
groups="purchase.group_purchase_manager,sf_base.group_purchase_director,sf_base.group_purchase"
action="purchase.action_purchase_order_report_all"/>
</odoo>

View File

@@ -86,6 +86,18 @@
</attribute>
</xpath>
<xpath expr="//form/sheet/div[@name='button_box']/button[@name='action_view_picking']"
position="replace">
<button type="object"
name="action_view_picking"
class="oe_stat_button"
icon="fa-truck" attrs="{'invisible':[('incoming_picking_count','=', 0)]}"
groups="stock.group_stock_user,sf_base.group_purchase,sf_base.group_purchase_director">
<field name="incoming_picking_count" widget="statinfo" string="收货"
help="Incoming Shipments"/>
</button>
</xpath>
<xpath expr="//field[@name='order_line']" position="attributes">
<attribute name="attrs">{'readonly': [('state', 'in', ['purchase'])]}
</attribute>

View File

@@ -6,6 +6,14 @@
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='action_view_delivery']" position="attributes">
<attribute name="groups">sf_base.group_sale_salemanager,sf_base.group_sale_director</attribute>
</xpath>
<xpath expr="//button[@name='action_view_mrp_production']" position="attributes">
<attribute name="groups">
mrp.group_mrp_user,sf_base.group_sale_salemanager,sf_base.group_sale_director
</attribute>
</xpath>
<xpath expr="//field[@name='user_id']" position="replace">
<field name="user_id" widget="many2one_avatar_user" context="{'is_sale': True }"/>
</xpath>
@@ -34,16 +42,15 @@
<!-- </xpath>-->
<xpath expr="//form/header/button[@name='action_confirm'][2]" position="replace">
<button name="action_confirm" data-hotkey="v"
string="确认" type="object" context="{'validate_analytic': True}"
attrs="{'invisible': ['|','&amp;',('check_status', '!=', 'approved'),('state', 'in', ['draft','cancel']),'&amp;','&amp;',('check_status', '=', 'approved'),('state', 'in', ['sale','cancel']),('delivery_status', '!=', False)]}"/>
string="确认接单" type="object" context="{'validate_analytic': True}"
attrs="{'invisible': ['|', ('state', 'in', ['cancel']), '|','&amp;',('check_status', '!=', 'approved'),('state', 'in', ['draft','cancel']),'&amp;','&amp;',('check_status', '=', 'approved'),('state', 'in', ['sale','cancel']),('delivery_status', '!=', False)]}"/>
</xpath>
<xpath expr="//form/header/button[@name='action_cancel']" position="attributes">
<attribute name="attrs">{'invisible': ['|','&amp;',('state', 'in',
['cancel','draft']),('check_status',
'in',
[False,'approved']),'&amp;',('check_status', '=', 'approved'),('state', 'in',
['sale','cancel','draft'])]}
<attribute name="attrs">{'invisible': ['|', ('state', 'in', ['cancel']), '|','&amp;',
('check_status', '!=', 'approved'),('state', 'in', ['draft','cancel']),'&amp;','&amp;',('check_status',
'=', 'approved'),('state', 'in', ['sale','cancel']),('delivery_status', '!=', False)]}
</attribute>
<attribute name="string">拒绝接单</attribute>
</xpath>
<xpath expr="//form/header/button[@name='action_draft']" position="attributes">
<attribute name="invisible">1</attribute>
@@ -123,6 +130,9 @@
<field name="signature" position="attributes">
<attribute name="attrs">{'readonly': [('state', 'in', ['cancel','sale'])]}</attribute>
</field>
<xpath expr="//button[@name='action_cancel']" position="attributes">
<attribute name="string">拒绝接单</attribute>
</xpath>
</field>
</record>

View File

@@ -94,47 +94,47 @@ class MachineTableToolChangingApply(models.Model):
if len(records) > 1:
raise ValidationError('该刀位号已存在,请重新选择!!!')
@api.constrains('functional_tool_status')
def automation_apply_for_tool_change(self):
"""
自动申请换刀
:return:
"""
# 更新数据到机台换刀申请界面
if self.functional_tool_status == '报警' and not self.sf_functional_tool_assembly_id:
machine_table_tool_changing_apply = self.env['sf.machine.table.tool.changing.apply'].search(
[('maintenance_equipment_id', '=', self.maintenance_equipment_id.id),
('cutter_spacing_code_id', '=', self.cutter_spacing_code_id.id)
])
# 创建功能刀具预警记录
self.env['sf.functional.tool.warning'].create_tool_warning_record({'tool_changing_apply_id': self})
# 新建组装任务
sf_functional_tool_assembly = self.env['sf.functional.tool.assembly'].create({
'functional_tool_name': self.functional_tool_name,
'functional_tool_type_id': self.functional_tool_type_id.id,
'functional_tool_diameter': self.diameter,
'knife_tip_r_angle': self.knife_tip_r_angle,
'coarse_middle_thin': '3',
'new_former': '0',
'functional_tool_length': self.extension_length,
'effective_length': self.effective_length,
'loading_task_source': '1',
'use_tool_time': fields.Datetime.now() + timedelta(hours=4),
'production_line_name_id': self.production_line_id.id,
'machine_tool_name_id': self.maintenance_equipment_id.id,
'applicant': '系统自动',
'apply_time': fields.Datetime.now(),
'cutter_spacing_code_id': self.cutter_spacing_code_id.id,
'whether_standard_knife': self.whether_standard_knife,
'reason_for_applying': '机台报警自动换刀',
'sf_machine_table_tool_changing_apply_id': self.id
})
machine_table_tool_changing_apply.write(
{'status': '1',
'sf_functional_tool_assembly_id': sf_functional_tool_assembly.id})
# @api.constrains('functional_tool_status')
# def automation_apply_for_tool_change(self):
# """
# 自动申请换刀
# :return:
# """
# # 更新数据到机台换刀申请界面
# if self.functional_tool_status == '报警' and not self.sf_functional_tool_assembly_id:
# machine_table_tool_changing_apply = self.env['sf.machine.table.tool.changing.apply'].search(
# [('maintenance_equipment_id', '=', self.maintenance_equipment_id.id),
# ('cutter_spacing_code_id', '=', self.cutter_spacing_code_id.id)
# ])
#
# # 创建功能刀具预警记录
# self.env['sf.functional.tool.warning'].create_tool_warning_record({'tool_changing_apply_id': self})
#
# # 新建组装任务
# sf_functional_tool_assembly = self.env['sf.functional.tool.assembly'].create({
# 'functional_tool_name': self.functional_tool_name,
# 'functional_tool_type_id': self.functional_tool_type_id.id,
# 'functional_tool_diameter': self.diameter,
# 'knife_tip_r_angle': self.knife_tip_r_angle,
# 'coarse_middle_thin': '3',
# 'new_former': '0',
# 'functional_tool_length': self.extension_length,
# 'effective_length': self.effective_length,
# 'loading_task_source': '1',
# 'use_tool_time': fields.Datetime.now() + timedelta(hours=4),
# 'production_line_name_id': self.production_line_id.id,
# 'machine_tool_name_id': self.maintenance_equipment_id.id,
# 'applicant': '系统自动',
# 'apply_time': fields.Datetime.now(),
# 'cutter_spacing_code_id': self.cutter_spacing_code_id.id,
# 'whether_standard_knife': self.whether_standard_knife,
# 'reason_for_applying': '机台报警自动换刀',
# 'sf_machine_table_tool_changing_apply_id': self.id
# })
#
# machine_table_tool_changing_apply.write(
# {'status': '1',
# 'sf_functional_tool_assembly_id': sf_functional_tool_assembly.id})
def revocation_1(self):
"""
@@ -760,6 +760,15 @@ class FunctionalToolDismantle(models.Model):
functional_tool_id = fields.Many2one('sf.functional.cutting.tool.entity', '功能刀具', required=True, tracking=True,
domain=[('functional_tool_status', '!=', '已拆除'),
('current_location', '=', '刀具房')])
@api.onchange('functional_tool_id')
def _onchange_functional_tool_id(self):
for item in self:
if item:
dismantle_id = self.search([('functional_tool_id', '=', item.functional_tool_id.id)])
if dismantle_id:
raise ValidationError(f'Rfid为【{item.rfid}】的功能刀具已经存在拆解单,单号是【{dismantle_id[0].code}')
tool_type_id = fields.Many2one('sf.functional.cutting.tool.model', string='功能刀具类型', store=True,
compute='_compute_functional_tool_num')
tool_groups_id = fields.Many2one('sf.tool.groups', '刀具组', compute='_compute_functional_tool_num', store=True)
@@ -938,15 +947,23 @@ class FunctionalToolDismantle(models.Model):
if self.chuck_freight_id == self.pad_freight_id:
raise ValidationError('【夹头】和【刀盘】的目标货位重复,请重新选择!')
def tool_scrap(self):
self.scrap_boolean = True
def tool_no_scrap(self):
self.scrap_boolean = False
def confirmation_disassembly(self):
logging.info('%s刀具确认开始拆解' % self.dismantle_cause)
code = self.code
if self.functional_tool_id.functional_tool_status == '已拆除':
raise ValidationError('Rfid为【%s】的功能刀具已经拆解,请勿重复操作!' % self.functional_tool_id.rfid_dismantle)
raise ValidationError('Rfid为【%s名称为【%s的功能刀具已经拆解,请勿重复操作!' % (
self.functional_tool_id.rfid_dismantle, self.name))
# 对拆解的功能刀具进行校验,只有在刀具房的功能刀具才能拆解
if self.functional_tool_id.tool_room_num == 0:
raise ValidationError('Rfid为【%s】的功能刀具当前位置为【%s】,不能进行拆解!' % (
self.rfid, self.functional_tool_id.current_location))
elif self.functional_tool_id.functional_tool_status != '报警':
if self.functional_tool_id.tool_room_num == 0:
raise ValidationError('Rfid为【%s】的功能刀具当前位置为【%s】,不能进行拆解!' % (
self.rfid, self.functional_tool_id.current_location))
# 目标重复校验
self.location_duplicate_check()
datas = {'scrap': [], 'picking': []}
@@ -1005,6 +1022,14 @@ class FunctionalToolDismantle(models.Model):
'rfid': '%s(已拆解)' % self.rfid,
'state': '已拆解'
})
# ==================修改刀具预警信息的值============
warning_id = self.env['sf.functional.tool.warning'].sudo().search(
[('functional_tool_id', '=', self.functional_tool_id.id)])
if warning_id:
warning_id.sudo().write({
'dispose_user': self.env.user.name,
'dispose_time': fields.Datetime.now()
})
logging.info('%s】刀具拆解成功!' % self.name)
def create_tool_picking_scrap(self, datas):
@@ -1103,7 +1128,7 @@ class StockMove(models.Model):
move_line_ids = picking_id.move_line_ids
for move_line_id in move_line_ids:
for res in data:
if move_line_id.lot_id.product_id == res['lot_id'].product_id:
if move_line_id.product_id == res['lot_id'].product_id:
move_line_id.write({
'destination_location_id': res.get('destination').id,
'lot_id': res.get('lot_id').id

View File

@@ -11,6 +11,7 @@ from odoo.exceptions import ValidationError
class FunctionalCuttingToolEntity(models.Model):
_name = 'sf.functional.cutting.tool.entity'
_description = '功能刀具列表'
_order = 'functional_tool_status'
functional_tool_name_id = fields.Many2one('sf.functional.tool.assembly', string='功能刀具组装单', readonly=True)
@@ -53,6 +54,22 @@ class FunctionalCuttingToolEntity(models.Model):
safe_inventory_id = fields.Many2one('sf.real.time.distribution.of.functional.tools',
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.env['sf.functional.tool.dismantle'].sudo().create({
'functional_tool_id': item.ids[0],
'dismantle_cause': '寿命到期报废'
})
# 创建刀具报警记录
self.env['sf.functional.tool.warning'].sudo().create({
'rfid': item.rfid,
'functional_tool_id': item.ids[0]
})
@api.depends('barcode_id.quant_ids', 'barcode_id.quant_ids.location_id', 'functional_tool_status',
'current_shelf_location_id')
def _compute_current_location_id(self):
@@ -101,27 +118,28 @@ class FunctionalCuttingToolEntity(models.Model):
def tool_in_out_stock_location(self, location_id):
tool_room_id = self.env['stock.location'].search([('name', '=', '刀具房')])
pre_manufacturing_id = self.env['stock.location'].search([('name', '=', '制造前')])
for item in self:
# 中控反馈该位置有刀
if item:
# 系统该位置有刀
if location_id.product_sn_id:
# 中控反馈和系统中,该位置是同一把刀
if item.barcode_id == location_id.product_sn_id:
return True
# 中控反馈和系统中,该位置不是同一把刀
else:
# 原刀从线边出库
item.tool_in_out_stock_location_1(location_id, tool_room_id)
# 新刀入库到线边
item.create_stock_move(pre_manufacturing_id, location_id)
item.current_shelf_location_id = location_id.id
if self:
for item in self:
# 中控反馈该位置有刀
if item:
# 系统该位置有刀
if location_id.product_sn_id:
# 中控反馈和系统中,该位置是同一把刀
if item.barcode_id == location_id.product_sn_id:
return True
# 中控反馈和系统中,该位置不是同一把刀
else:
# 原刀从线边出库
item.tool_in_out_stock_location_1(location_id, tool_room_id)
# 新刀入库到线边
item.create_stock_move(pre_manufacturing_id, location_id)
item.current_shelf_location_id = location_id.id
# 中控反馈该位置没有刀
else:
# 系统该位置有刀
if location_id.product_sn_id:
item.tool_in_out_stock_location_1(location_id, tool_room_id)
# 中控反馈该位置没有刀
else:
# 系统该位置有刀
if location_id.product_sn_id:
self.tool_in_out_stock_location_1(location_id, tool_room_id)
def tool_in_out_stock_location_1(self, location_id, tool_room_id):
tool = self.env['sf.functional.cutting.tool.entity'].search(
@@ -238,10 +256,39 @@ class FunctionalCuttingToolEntity(models.Model):
functional_tool_model_ids.append(functional_tool_model.id)
return [(6, 0, functional_tool_model_ids)]
dismantle_num = fields.Integer('拆解单数量', compute='_compute_dismantle_num', store=True)
dismantle_ids = fields.One2many('sf.functional.tool.dismantle', 'functional_tool_id', '拆解单')
@api.depends('dismantle_ids')
def _compute_dismantle_num(self):
for item in self:
if item:
item.dismantle_num = len(item.dismantle_ids)
def open_functional_tool_dismantle_form(self):
self.ensure_one()
dismantle_ids = self.env['sf.functional.tool.dismantle'].sudo().search([('functional_tool_id', '=', self.id)])
action = {
'res_model': 'sf.functional.tool.dismantle',
'type': 'ir.actions.act_window',
'name': '拆解单',
}
if len(dismantle_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': dismantle_ids[0].id,
})
else:
action.update({
'domain': [('id', 'in', dismantle_ids.ids)],
'view_mode': 'tree,form',
})
return action
def open_functional_tool_warning(self):
action = self.env.ref('sf_tool_management.action_sf_functional_tool_warning')
result = action.read()[0]
result['domain'] = [('functional_tool_name_id', '=', self.functional_tool_name_id.id)]
result['domain'] = [('functional_tool_id', '=', self.id)]
return result
def open_stock_move_line(self):
@@ -323,10 +370,10 @@ class FunctionalToolWarning(models.Model):
_name = 'sf.functional.tool.warning'
_description = '功能刀具预警'
code = fields.Char('编码', related='functional_tool_name_id.code')
rfid = fields.Char('Rfid', related='functional_tool_name_id.rfid')
tool_groups_id = fields.Many2one('sf.tool.groups', '刀具组', related='functional_tool_name_id.tool_groups_id')
name = fields.Char('名称', invisible=True, readonly=True, related='functional_tool_name_id.name')
code = fields.Char('编码', related='functional_tool_id.code')
rfid = fields.Char('Rfid', readonly=True)
tool_groups_id = fields.Many2one('sf.tool.groups', '刀具组', related='functional_tool_id.tool_groups_id')
name = fields.Char('名称', invisible=True, readonly=True, related='functional_tool_id.name')
# 机床信息
production_line_id = fields.Many2one('sf.production.line', string='生产线',
group_expand='_read_group_machine_table_name_ids')
@@ -337,52 +384,77 @@ class FunctionalToolWarning(models.Model):
cutter_spacing_code_id = fields.Many2one('maintenance.equipment.tool', string='刀位号',
domain="[('equipment_id', '=', maintenance_equipment_id)]")
# 功能刀具信息
functional_tool_name_id = fields.Many2one('sf.functional.tool.assembly', string='功能刀具名称')
barcode_id = fields.Many2one('stock.lot', string='功能刀具序列号', related='functional_tool_name_id.barcode_id')
mrs_cutting_tool_type_id = fields.Many2one('sf.functional.cutting.tool.model', string='功能刀具类型')
diameter = fields.Float(string='刀具直径(mm)')
knife_tip_r_angle = fields.Float(string='尖R角(mm)')
functional_tool_id = fields.Many2one('sf.functional.cutting.tool.entity', string='功能刀具', readonly=True)
barcode_id = fields.Many2one('stock.lot', string='序列号', related='functional_tool_id.barcode_id')
mrs_cutting_tool_type_id = fields.Many2one('sf.functional.cutting.tool.model', string='功能刀具类型',
related='functional_tool_id.sf_cutting_tool_type_id')
diameter = fields.Float(string='具直径(mm)', related='functional_tool_id.functional_tool_diameter')
knife_tip_r_angle = fields.Float(string='刀尖R角(mm)', related='functional_tool_id.knife_tip_r_angle')
# 其他信息
install_tool_time = fields.Datetime("刀具组装时间", related='functional_tool_name_id.tool_loading_time')
on_board_time = fields.Datetime('上机装刀时间')
max_lifetime_value = fields.Integer(string='最大寿命值(min)')
alarm_value = fields.Integer(string='报警值(min)')
used_value = fields.Integer(string='已使用值(min)')
max_lifetime_value = fields.Integer(string='最大寿命值(min)', related='functional_tool_id.max_lifetime_value')
alarm_value = fields.Integer(string='报警值(min)', related='functional_tool_id.alarm_value')
used_value = fields.Integer(string='已使用值(min)', related='functional_tool_id.used_value')
functional_tool_status = fields.Selection([('正常', '正常'), ('报警', '报警'), ('已拆除', '已拆除')], string='状态')
alarm_time = fields.Datetime('报警时间')
dispose_user = fields.Char('处理人')
dispose_time = fields.Char('处理时间')
dispose_func = fields.Char('处理方法/措施', readonly=False)
alarm_time = fields.Datetime('报警时间', default=lambda self: fields.Datetime.now(), readonly=True)
dispose_user = fields.Char('处理人', readonly=True)
dispose_time = fields.Char('处理时间', readonly=True)
dispose_func = fields.Char('处理方法/措施', readonly=True)
active = fields.Boolean(string='已归档', default=True)
functional_tool_name_id = fields.Many2one('sf.functional.tool.assembly', string='功能刀具名称')
@api.model
def _read_group_machine_table_name_ids(self, categories, domain, order):
machine_table_name_ids = categories._search([], order=order, access_rights_uid=SUPERUSER_ID)
return categories.browse(machine_table_name_ids)
def create_tool_warning_record(self, obj):
"""
机台换刀申请报警状态时,创建功能刀具预警记录
"""
if obj:
for tool in obj.get('tool_changing_apply_id'):
self.env['sf.functional.tool.warning'].create({
'production_line_id': tool.production_line_id.id,
'maintenance_equipment_id': tool.maintenance_equipment_id.id,
'machine_tool_code': tool.machine_tool_code,
'machine_table_type_id': tool.machine_table_type_id.id,
'cutter_spacing_code_id': tool.cutter_spacing_code_id.id,
'functional_tool_name_id': tool.functional_tool_name_id.id,
'barcode_id': tool.barcode_id.id,
'diameter': tool.diameter,
'knife_tip_r_angle': tool.knife_tip_r_angle,
'max_lifetime_value': tool.max_lifetime_value,
'alarm_value': tool.alarm_value,
'used_value': tool.used_value,
'functional_tool_status': tool.functional_tool_status,
'alarm_time': fields.Datetime.now(),
})
def action_open_dismantle(self):
self.ensure_one()
dismantle_ids = self.env['sf.functional.tool.dismantle'].sudo().search(
[('functional_tool_id', '=', self.functional_tool_id.id)])
action = {
'res_model': 'sf.functional.tool.dismantle',
'type': 'ir.actions.act_window',
'name': '拆解单'
}
if len(dismantle_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': dismantle_ids.ids[0]
})
elif dismantle_ids:
action.update({
'view_mode': 'tree,form',
'domain': [('id', 'in', dismantle_ids.ids)],
})
else:
return False
return action
# def create_tool_warning_record(self, obj):
# """
# 机台换刀申请报警状态时,创建功能刀具预警记录
# """
# if obj:
# for tool in obj.get('tool_changing_apply_id'):
# self.env['sf.functional.tool.warning'].create({
# 'production_line_id': tool.production_line_id.id,
# 'maintenance_equipment_id': tool.maintenance_equipment_id.id,
# 'machine_tool_code': tool.machine_tool_code,
# 'machine_table_type_id': tool.machine_table_type_id.id,
# 'cutter_spacing_code_id': tool.cutter_spacing_code_id.id,
# 'functional_tool_name_id': tool.functional_tool_name_id.id,
# 'barcode_id': tool.barcode_id.id,
# 'diameter': tool.diameter,
# 'knife_tip_r_angle': tool.knife_tip_r_angle,
# 'max_lifetime_value': tool.max_lifetime_value,
# 'alarm_value': tool.alarm_value,
# 'used_value': tool.used_value,
# 'functional_tool_status': tool.functional_tool_status,
# 'alarm_time': fields.Datetime.now(),
# })
class StockMoveLine(models.Model):

View File

@@ -53,7 +53,7 @@ class SfMaintenanceEquipment(models.Model):
params = {"DeviceId": self.name}
r = requests.get(crea_url, params=params, headers=headers)
ret = r.json()
logging.info('register_equipment_tool:%s' % ret)
logging.info('机床刀库register_equipment_tool():%s' % ret)
datas = ret['Datas']
self.write_maintenance_equipment_tool(datas)
if ret['Succeed']:

View File

@@ -25,7 +25,10 @@
<field name="max_lifetime_value"/>
<field name="alarm_value"/>
<field name="used_value"/>
<field name="functional_tool_status"/>
<field name="functional_tool_status" widget='badge'
decoration-success="functional_tool_status == '正常'"
decoration-muted="functional_tool_status == '已拆除'"
decoration-danger="functional_tool_status == '报警'"/>
<field name="current_location" string="当前位置"/>
<field name="current_location_id" invisible="1"/>
@@ -48,6 +51,18 @@
<div class="oe_button_box" name="button_box">
<!-- <button name="button_safe_inventory_id" string="更新功能刀具关联的安全库存记录"-->
<!-- type="object" class="btn-primary"/>-->
<button class="oe_stat_button" groups="sf_base.group_sf_mrp_user"
name="open_functional_tool_dismantle_form"
icon="fa-credit-card"
type="object"
attrs="{'invisible': [('dismantle_num', '=', 0)]}">
<div name="dismantle_num" class="o_field_widget o_readonly_modifier o_field_statinfo">
<span class="o_stat_info o_stat_value">
<field name="dismantle_num"/>
</span>
<span class="o_stat_text">拆解单</span>
</div>
</button>
<button class="oe_stat_button" groups="sf_base.group_sf_mrp_user"
name="open_functional_tool_warning"
icon="fa-list-ul"
@@ -235,26 +250,29 @@
<field name="name">sf.functional.tool.warning.tree</field>
<field name="model">sf.functional.tool.warning</field>
<field name="arch" type="xml">
<tree string="功能刀具预警" create="0" edit="0" delete="0" editable="bottom">
<field name="production_line_id" optional="hide"/>
<field name="maintenance_equipment_id" optional="hide"/>
<field name="machine_tool_code"/>
<field name="cutter_spacing_code_id"/>
<field name="barcode_id" invisible="1"/>
<tree string="功能刀具预警" create="0" edit="0" delete="0" editable="bottom" default_order="id desc"
action="action_open_dismantle" type="object">
<field name="production_line_id" invisible="1"/>
<field name="maintenance_equipment_id" invisible="1"/>
<field name="machine_tool_code" invisible="1"/>
<field name="cutter_spacing_code_id" invisible="1"/>
<field name="on_board_time" invisible="1"/>
<field name="functional_tool_status" invisible="1"/>
<field name="functional_tool_name_id" invisible="1"/>
<field name="rfid"/>
<field name="functional_tool_name_id"/>
<field name="diameter"/>
<field name="knife_tip_r_angle"/>
<field name="functional_tool_id"/>
<field name="barcode_id" optional="hide"/>
<field name="diameter" optional="hide"/>
<field name="knife_tip_r_angle" optional="hide"/>
<field name="install_tool_time" optional="hide"/>
<field name="on_board_time" optional="hide"/>
<field name="max_lifetime_value"/>
<field name="alarm_value"/>
<field name="used_value"/>
<field name="functional_tool_status"/>
<field name="alarm_time"/>
<field name="dispose_user"/>
<field name="dispose_time"/>
<field name="dispose_func"/>
<field name="dispose_func" optional="hide"/>
<!-- <button name="enroll_functional_tool_warning" string="刀具预警注册" type="object"-->
<!-- class="btn-primary"/>-->
</tree>
@@ -266,31 +284,19 @@
<field name="model">sf.functional.tool.warning</field>
<field name="arch" type="xml">
<search string="功能刀具预警">
<field name="machine_tool_code"/>
<field name="cutter_spacing_code_id"/>
<field name="barcode_id"/>
<field name="rfid"/>
<field name="functional_tool_name_id"/>
<field name="diameter"/>
<field name="knife_tip_r_angle"/>
<field name="install_tool_time" optional="hide"/>
<field name="on_board_time" optional="hide"/>
<field name="max_lifetime_value"/>
<field name="alarm_value"/>
<field name="used_value"/>
<field name="functional_tool_status"/>
<field name="alarm_time"/>
<field name="dispose_user"/>
<field name="dispose_time"/>
<field name="dispose_func"/>
<field name="production_line_id" invisible="True"/>
<filter string="已归档" name="inactive" domain="[('active', '=', False)]"/>
<searchpanel>
<field name="production_line_id" icon="fa-building" enable_counters="1"/>
<field name="maintenance_equipment_id" icon="fa-building" enable_counters="1"/>
<field name="cutter_spacing_code_id" icon="fa-building" enable_counters="1"/>
<field name="functional_tool_status" icon="fa-building" enable_counters="1"/>
</searchpanel>
<group expand="0">
<filter string="报警时间" name="alarm_time" domain="[]"
context="{'group_by': 'alarm_time'}"/>
</group>
</search>
</field>
</record>

View File

@@ -704,10 +704,10 @@
<field name="model">sf.functional.tool.assembly</field>
<field name="arch" type="xml">
<search>
<field name="functional_tool_name"/>
<field name="assembly_order_code"/>
<field name="code" string="功能刀具编码"/>
<field name="barcode_id"/>
<field name="functional_tool_name"/>
<field name="functional_tool_type_id"/>
<field name="tool_groups_id"/>
<field name="loading_task_source" string="任务来源"/>
@@ -800,10 +800,16 @@
</h1>
</div>
<field name="_barcode_scanned" widget="barcode_handler"/>
<script>
setTimeout(function(){
$('#functional_tool_id').blur()
}, 100)
</script>
<group>
<group>
<field name="functional_tool_id" placeholder="请选择将要拆解的功能刀具"
options="{'no_create': True}" attrs="{'readonly': [('state', '=', '已拆解')]}"/>
options="{'no_create': True}"
attrs="{'readonly': ['|',('state', '=', '已拆解'),('id', '!=', False)]}"/>
<field name="rfid" attrs="{'invisible': [('rfid', '=', '')]}"/>
<field name="rfid_dismantle" attrs="{'invisible': [('rfid_dismantle', '=', False)]}"/>
<field name="tool_type_id"/>
@@ -833,10 +839,26 @@
<notebook>
<page string="物料组装信息">
<group>
<group string="刀柄" attrs="{'invisible': [('handle_product_id', '=', False)]}">
<group string="刀柄" attrs="{'invisible': [('handle_product_id', '=', False)]}"
col="1">
<group attrs="{'invisible': [('dismantle_cause', 'not in', ['寿命到期报废','崩刀报废'])]}">
<!-- <group col="3">-->
<group>
<field name="scrap_boolean" string="是否报废" readonly="0"/>
</group>
<!-- <group></group>-->
<!-- <group>-->
<!-- <button string="报废" name="tool_scrap" type="object"-->
<!-- class="btn-primary" confirm="是否确认报废刀柄"-->
<!-- attrs="{'invisible': [('scrap_boolean', '=', True)]}"/>-->
<!-- <button string="取消" name="tool_no_scrap" type="object"-->
<!-- class="btn-primary" confirm="是否取消报废刀柄"-->
<!-- attrs="{'invisible': [('scrap_boolean', '=', False)]}"/>-->
<!-- <group></group>-->
<!-- </group>-->
<!-- </group>-->
</group>
<group>
<field name="scrap_boolean" string="是否报废"
attrs="{'invisible': [('dismantle_cause', 'not in', ['寿命到期报废','崩刀报废'])], 'readonly': [('state', '=', '已拆解')]}"/>
<field name="handle_rfid" string="Rfid"/>
<field name="handle_lot_id" string="序列号"/>
<field name="handle_product_id" string="名称"/>
@@ -911,7 +933,7 @@
</group>
</group>
</page>
<page string="报废"
<page string="报废"
attrs="{'invisible':[('dismantle_cause', 'not in', ['寿命到期报废','崩刀报废'])]}">
<field name="scrap_ids">
<tree>
@@ -952,8 +974,9 @@
<field name="model">sf.functional.tool.dismantle</field>
<field name="arch" type="xml">
<search>
<field name="rfid"/>
<field name="functional_tool_id"/>
<field name="code" string="拆解单编码"/>
<field name="code" string="拆解单"/>
<filter name="no_dismantle_state" string="未拆解" domain="[('state','!=','已拆解')]"/>
<filter name="dismantle_state" string="已拆解" domain="[('state','=','已拆解')]"/>
<separator/>

View File

@@ -387,9 +387,9 @@ class FunctionalToolAssemblyOrder(models.TransientModel):
lot_ids = self.env['stock.lot'].sudo().search([('rfid', '=', barcode)])
if lot_ids:
for lot_id in lot_ids:
if lot_id.quant_ids[-1].location_id.name in '刀具房':
if lot_id.tool_material_status == '可用':
record.handle_code_id = lot_id.id
elif lot_id.quant_ids[-1].location_id.name == '刀具组装位置':
elif lot_id.quant_ids[-1].location_id.name in ['刀具组装位置']:
raise ValidationError('该刀柄已使用,请重新扫描!!!')
else:
raise ValidationError('该刀柄未入库,请重新扫描!!!')
@@ -842,6 +842,8 @@ class StockPicking(models.Model):
stock_move_id = self.env['stock.move']
datas = {'data': [], 'picking_id': picking_id}
if obj.handle_code_id:
# 修改刀柄序列号状态为【在用】
obj.handle_code_id.sudo().write({'tool_material_status': '在用'})
datas['data'].append(
{'current_location_id': self.env['sf.shelf.location'], 'lot_id': obj.handle_code_id})
if obj.integral_product_id:

View File

@@ -201,6 +201,11 @@
</group>
</group>
</group>
<script>
setTimeout(function(){
$('#handle_code_id').blur()
}, 100)
</script>
<group string="组装物料信息" col="1">
<field name="_barcode_scanned" widget="barcode_handler"/>
<group col="1">
@@ -368,7 +373,7 @@
<group>
<field name="obtain_measurement_status" invisible="1"/>
<button name="get_tool_preset_parameter" string="获取测量值" type="object"
attrs="{'invisible': [('enable_tool_presetter', '=', False)]}"
attrs="{'invisible': [('enable_tool_presetter', '=', False)]}"
class="btn-primary"/>
</group>
</group>

View File

@@ -507,13 +507,13 @@ class ShelfLocation(models.Model):
print('eeeeeee空闲', e)
# 调取获取货位信息接口
def get_sf_shelf_location_info(self):
def get_sf_shelf_location_info(self, device_id='Cabinet-AL'):
config = self.env['res.config.settings'].get_values()
headers = {'Authorization': config['center_control_Authorization']}
crea_url = config['center_control_url'] + "/AutoDeviceApi/GetLocationInfos"
params = {'DeviceId': 'Cabinet-AL'}
params = {'DeviceId': device_id}
r = requests.get(crea_url, params=params, headers=headers)
ret = r.json()

View File

@@ -13,88 +13,92 @@ class MrsShelfLocationDataSync(models.Model):
_name = 'sf.shelf.location.datasync'
_description = '同步库存信息'
def get_total_data(self):
# 建立对应关系的函数
def align_data(my_data, their_data):
paired_data = list(zip(my_data, their_data))
return paired_data
logging.info('============================get_total_data()======================')
shelf_1_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-一号线边刀架')], limit=1)
tool_location_objs_1 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_1_obj.id)], order='id')
location_codes_1 = [location.barcode for location in tool_location_objs_1]
print(location_codes_1)
# 对方的数据列表
their_data_1 = [f"ToolCab1-{i:02}" for i in range(1, 73)]
# 执行对齐
aligned_data_1 = align_data(location_codes_1, their_data_1)
# 2
shelf_2_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-二号线边刀架')], limit=1)
tool_location_objs_2 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_2_obj.id)], order='id')
location_codes_2 = [location.barcode for location in tool_location_objs_2]
print(location_codes_2)
# 对方的数据列表
their_data_2 = [f"ToolCab2-{i:02}" for i in range(1, 73)]
# 执行对齐
aligned_data_2 = align_data(location_codes_2, their_data_2)
# 4
shelf_4_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-一号线边料架')], limit=1)
tool_location_objs_4 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_4_obj.id)], order='id')
location_codes_4 = [location.barcode for location in tool_location_objs_4]
print(location_codes_4)
# 对方的数据列表
their_data_4 = [f"PartCab4-{i:02}" for i in range(1, 17)]
# 执行对齐
aligned_data_4 = align_data(location_codes_4, their_data_4)
# 3
shelf_3_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-二号线边料架')], limit=1)
tool_location_objs_3 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_3_obj.id)], order='id')
location_codes_3 = [location.barcode for location in tool_location_objs_3]
print(location_codes_3)
# 对方的数据列表
their_data_3 = [f"PartCab3-{i:02}" for i in range(1, 13)]
# 执行对齐
aligned_data_3 = align_data(location_codes_3, their_data_3)
# 5
shelf_5_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-三号线边料架')], limit=1)
tool_location_objs_5 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_5_obj.id)], order='id')
location_codes_5 = [location.barcode for location in tool_location_objs_5]
print(location_codes_5)
# 对方的数据列表
their_data_5 = [f"PartCab5-{i:02}" for i in range(1, 13)]
# 执行对齐
aligned_data_5 = align_data(location_codes_5, their_data_5)
total_data = aligned_data_1 + aligned_data_2 + aligned_data_3 + aligned_data_4 + aligned_data_5
print(total_data)
logging.info(f"total_data: {total_data}")
return total_data
def find_our_code(self, total_data, their_code):
for code_pair in total_data:
if code_pair[1] == their_code:
return code_pair[0]
return None # 如果没有找到对应的值返回None或适当的默认值
def _cron_shelf_location_datasync(self):
try:
# 建立对应关系的函数
def align_data(my_data, their_data):
paired_data = list(zip(my_data, their_data))
return paired_data
shelf_1_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-一号线边刀架')], limit=1)
tool_location_objs_1 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_1_obj.id)], order='id')
location_codes_1 = [location.barcode for location in tool_location_objs_1]
print(location_codes_1)
# 对方的数据列表
their_data_1 = [f"ToolCab1-{i:02}" for i in range(1, 73)]
# 执行对齐
aligned_data_1 = align_data(location_codes_1, their_data_1)
# 2
shelf_2_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-二号线边刀架')], limit=1)
tool_location_objs_2 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_2_obj.id)], order='id')
location_codes_2 = [location.barcode for location in tool_location_objs_2]
print(location_codes_2)
# 对方的数据列表
their_data_2 = [f"ToolCab2-{i:02}" for i in range(1, 73)]
# 执行对齐
aligned_data_2 = align_data(location_codes_2, their_data_2)
# 4
shelf_4_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-一号线边料架')], limit=1)
tool_location_objs_4 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_4_obj.id)], order='id')
location_codes_4 = [location.barcode for location in tool_location_objs_4]
print(location_codes_4)
# 对方的数据列表
their_data_4 = [f"PartCab4-{i:02}" for i in range(1, 17)]
# 执行对齐
aligned_data_4 = align_data(location_codes_4, their_data_4)
# 3
shelf_3_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-二号线边料架')], limit=1)
tool_location_objs_3 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_3_obj.id)], order='id')
location_codes_3 = [location.barcode for location in tool_location_objs_3]
print(location_codes_3)
# 对方的数据列表
their_data_3 = [f"PartCab3-{i:02}" for i in range(1, 13)]
# 执行对齐
aligned_data_3 = align_data(location_codes_3, their_data_3)
# 5
shelf_5_obj = self.env['sf.shelf'].search([('name', '=', '一号产线-三号线边料架')], limit=1)
tool_location_objs_5 = self.env['sf.shelf.location'].search([('shelf_id', '=', shelf_5_obj.id)], order='id')
location_codes_5 = [location.barcode for location in tool_location_objs_5]
print(location_codes_5)
# 对方的数据列表
their_data_5 = [f"PartCab5-{i:02}" for i in range(1, 13)]
# 执行对齐
aligned_data_5 = align_data(location_codes_5, their_data_5)
total_data = aligned_data_1 + aligned_data_2 + aligned_data_3 + aligned_data_4 + aligned_data_5
print(total_data)
logging.info(f"total_data: {total_data}")
def find_their_code(my_code, aligned_data):
for code_pair in aligned_data:
if code_pair[0] == my_code:
return code_pair[1]
return None # 如果没有找到对应的值返回None或适当的默认值
def find_our_code(their_code, aligned_data):
for code_pair in aligned_data:
if code_pair[1] == their_code:
return code_pair[0]
return None # 如果没有找到对应的值返回None或适当的默认值
# 定时更新所有设备机床刀库信息
equipment_ids = self.env['maintenance.equipment'].search(
[('equipment_type', '=', '机床'), ('function_type', '!=', False)])
@@ -103,9 +107,11 @@ class MrsShelfLocationDataSync(models.Model):
equipment_id.register_equipment_tool()
shelfinfo = self.env['sf.shelf.location'].get_sf_shelf_location_info()
total_data = self.get_total_data()
print('shelfinfo:', shelfinfo)
for item in shelfinfo:
shelf_barcode = find_our_code(item['Postion'], total_data)
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:
# 如果是线边刀库信息,则对功能刀具移动生成记录
@@ -115,8 +121,16 @@ class MrsShelfLocationDataSync(models.Model):
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:
@@ -124,7 +138,6 @@ class MrsShelfLocationDataSync(models.Model):
else:
location_id.product_sn_id = False
logging.info('货架已获取信息:%s' % item)
except Exception as e:
logging.info("捕获错误信息:%s" % e)
raise ValidationError("数据错误导致同步失败,请联系管理员")

View File

@@ -21,7 +21,8 @@
<!-- ]"/> -->
<field name="destination_location_id" domain="[('location_id', '=', location_dest_id_value), '|',
('location_status', '=', '空闲'), ('product_id', '=', current_product_id), ('product_sn_id',
'=', there_is_no_sn)]" options="{'no_create': True,'no_create_edit':True}"/>
'=', there_is_no_sn), ('rotative_Boolean', '=', False)]"
options="{'no_create': True,'no_create_edit':True}"/>
<field name="rfid_barcode" string="Rfid"/>
<!-- <field name="location_dest_id_product_type"/> -->