Compare commits

...

210 Commits

Author SHA1 Message Date
liaodanlong
4a22071f94 Merge branch 'refs/heads/develop' into feature/tax_sync
# Conflicts:
#	sf_manufacturing/models/res_config_setting.py
#	sf_manufacturing/views/res_config_settings_views.xml
#	sf_mrs_connect/models/res_config_setting.py
2024-09-20 10:35:47 +08:00
liaodanlong
ec2075c874 刀具岗权限调整 2024-09-20 10:32:30 +08:00
liaodanlong
ebc9716019 agv 配置调整位置 2024-09-20 10:32:08 +08:00
胡尧
72c9c9872e Accept Merge Request #1326: (feature/流程用扫码完成 -> develop)
Merge Request: 修复扫码确认后接驳站点被清除的bug

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1326?initial=true
2024-09-20 09:39:21 +08:00
胡尧
2b541fe041 修复扫码确认后接驳站点被清除的bug 2024-09-20 09:38:32 +08:00
廖丹龙
ffb32c7ce2 Accept Merge Request #1325: (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/1325?initial=true
2024-09-19 17:38:32 +08:00
liaodanlong
3cacca80e9 隐藏新建字段 2024-09-19 17:35:57 +08:00
禹翔辉
a3356fe195 Accept Merge Request #1324: (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/1324
2024-09-19 17:29:29 +08:00
yuxianghui
3c70e1623e Merge branch 'feature/工单状态优化' into feature/库存优化 2024-09-19 17:27:44 +08:00
yuxianghui
51628c081a 1、禁用超级管理员之外的角色通过库存概览跳转到调拨单界面的调拨单创建按钮,隐藏制造作业类型的单据;2、优化移动历史记录。 2024-09-19 17:27:02 +08:00
杨金灵
18ebe6bcb4 Accept Merge Request #1323: (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/1323
2024-09-19 17:25:05 +08:00
jinling.yang
63ac4f2b44 返工工单添加标签字段 2024-09-19 17:23:01 +08:00
liaodanlong
1b6ca170c0 bom组装默认值 2024-09-19 15:15:30 +08:00
马广威
2966260f51 Accept Merge Request #1322: (feature/优化制造功能 -> develop)
Merge Request: 解除装夹处,扫码同步打印成品条码

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1322?initial=true
2024-09-19 15:03:30 +08:00
mgw
c4069995e0 解除装夹处,扫码同步打印成品条码 2024-09-19 14:38:24 +08:00
管欢
54920982a2 Accept Merge Request #1319: (feature/org_info_synchronous -> develop)
Merge Request: 员工信息企微id同步

Created By: @管欢
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @管欢
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1319
2024-09-19 13:52:30 +08:00
廖丹龙
f947a3ed58 Accept Merge Request #1321: (feature/tax_sync -> develop)
Merge Request: bom清单问题修复

Created By: @廖丹龙
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1321?initial=true
2024-09-19 13:50:43 +08:00
liaodanlong
5e86d67316 bom清单问题修复 2024-09-19 13:42:52 +08:00
马广威
7354a19696 Accept Merge Request #1320: (feature/优化制造功能 -> develop)
Merge Request: 扫码获取装夹检测结果

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1320?initial=true
2024-09-19 10:51:20 +08:00
mgw
78e3d77000 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-09-19 10:50:06 +08:00
mgw
47ad2410cf 调整agv参数位置 2024-09-19 10:16:09 +08:00
mgw
fa2eb7b3cf 增加是否获取文件判断 2024-09-19 10:07:26 +08:00
guanhuan
0cee6ebd77 员工信息同步 2024-09-19 09:48:01 +08:00
杨金灵
73f161ffa4 Accept Merge Request #1318: (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/1318?initial=true
2024-09-19 09:34:41 +08:00
jinling.yang
ff06d5c3be 消息模版添加时间节点 2024-09-19 09:32:04 +08:00
liaodanlong
78cb177ac5 Merge remote-tracking branch 'origin/develop' into develop 2024-09-19 09:14:33 +08:00
liaodanlong
9601502360 视图菜单与action顺序调整 2024-09-19 09:12:38 +08:00
mgw
739b7e600c Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-09-19 09:04:20 +08:00
mgw
27da7639b2 增加扫码点击按钮功能 2024-09-19 09:04:05 +08:00
mgw
258e3bdb9f 一般设置页增加重新获取检测文件配置项目 2024-09-19 09:03:07 +08:00
jinling.yang
7f3e9927d5 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-19 08:52:34 +08:00
jinling.yang
927f726030 Merge branch 'feature/优化消息模版' into develop 2024-09-19 08:52:25 +08:00
杨金灵
7576200fc4 Accept Merge Request #1315: (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/1315?initial=true
2024-09-19 08:52:10 +08:00
胡尧
4ffb9d636f Accept Merge Request #1316: (feature/流程用扫码完成 -> develop)
Merge Request: 修改扫码判断起点接驳站逻辑

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1316?initial=true
2024-09-19 08:51:03 +08:00
jinling.yang
24f0547343 消息模块添加销售依赖 2024-09-19 08:50:24 +08:00
胡尧
402a323673 修改扫码判断起点接驳站逻辑 2024-09-19 08:49:23 +08:00
jinling.yang
5e1685417f Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-19 08:46:01 +08:00
jinling.yang
9dfe34ce9a 还原注释代码 2024-09-19 08:45:50 +08:00
jinling.yang
190d6da217 还原代码 2024-09-18 17:59:08 +08:00
胡嘉莹
8946b44280 Accept Merge Request #1313: (feature/update_production_line -> develop)
Merge Request: 修改计划排程,新增处理排程计划订单交货时间为null数据处理的方法

Created By: @胡嘉莹
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @胡嘉莹
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1313
2024-09-18 17:44:45 +08:00
廖丹龙
f21d085462 Accept Merge Request #1314: (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/1314
2024-09-18 17:42:28 +08:00
jinling.yang
43f53197c4 优化消息模版 2024-09-18 17:39:06 +08:00
liaodanlong
ac43be1262 适用刀具物料类型字段 设置只能去选择。取消必填 2024-09-18 16:36:52 +08:00
jinling.yang
8bf1c56efd Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化消息模版
# Conflicts:
#	sf_tool_management/models/base.py
2024-09-18 15:40:54 +08:00
jinling.yang
d66791dc41 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-18 15:39:50 +08:00
jinling.yang
ce6b36a77e 12 2024-09-18 15:39:42 +08:00
hujiaying
4acae01009 修改计划排程,新增处理排程计划订单交货时间为null数据处理的方法 2024-09-18 15:34:04 +08:00
胡尧
213b76a280 Accept Merge Request #1312: (feature/流程用扫码完成 -> develop)
Merge Request: 解除装夹工单页面进入时失去焦点

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1312
2024-09-18 15:15:10 +08:00
胡尧
c675e1d67c 解除装夹工单页面进入时失去焦点 2024-09-18 15:13:20 +08:00
胡嘉莹
8a9f6ab34f Accept Merge Request #1311: (feature/update_production_line -> 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/1311
2024-09-18 14:14:50 +08:00
hujiaying
8aca6ce084 修改计划排程甘特图界面不显示已经排程的制造订单 2024-09-18 14:03:04 +08:00
禹翔辉
cb0c093006 Accept Merge Request #1310: (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/1310?initial=true
2024-09-18 11:56:37 +08:00
yuxianghui
1e7d7008b4 Merge branch 'feature/工单优化' into feature/工单状态优化 2024-09-18 11:55:02 +08:00
yuxianghui
ef453dbc1e 工单状态优化 2024-09-18 11:53:46 +08:00
jinling.yang
9f1e635c8d Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-18 10:23:51 +08:00
jinling.yang
92037f3f04 注释OCC代码 2024-09-18 10:23:42 +08:00
禹翔辉
4ba0566d27 Accept Merge Request #1309: (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/1309?initial=true
2024-09-18 09:46:36 +08:00
yuxianghui
5d870d53f6 Merge branch 'feature/刀具拆解单优化' into feature/工单优化 2024-09-18 09:30:49 +08:00
yuxianghui
6a674cdb5b 工单状态优化 2024-09-18 09:28:36 +08:00
胡嘉莹
fbce132173 Accept Merge Request #1308: (feature/update_production_line -> 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/1308
2024-09-14 17:45:18 +08:00
guanhuan
98e13cba99 员工信息企微id同步 2024-09-14 17:45:00 +08:00
liaodanlong
7ce2f6c797 历史销售订单价格问题 2024-09-14 17:40:45 +08:00
hujiaying
7502792438 修改计划排程列表进入待排程的制造订单详情进行排程-报错 2024-09-14 17:26:24 +08:00
hujiaying
9efb13cf01 修改排程计划开始时间修改 2024-09-14 16:34:47 +08:00
管欢
b14e263a2e Accept Merge Request #1306: (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/1306
2024-09-14 14:40:58 +08:00
马广威
6f41ef3047 Accept Merge Request #1307: (feature/优化制造功能 -> develop)
Merge Request: 增加拉取文件之前的检查

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1307?initial=true
2024-09-14 14:34:02 +08:00
mgw
de29f0c938 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-09-14 14:32:55 +08:00
mgw
589a24f595 增加拉取文件之前的检查 2024-09-14 14:32:40 +08:00
胡嘉莹
85186f94f5 Accept Merge Request #1302: (feature/update_production_line -> develop)
Merge Request: 优化计划开始时间默认往后延迟10分钟

Created By: @胡嘉莹
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @胡嘉莹
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1302
2024-09-14 14:31:56 +08:00
廖丹龙
ffc363a31c Accept Merge Request #1305: (feature/tax_sync -> develop)
Merge Request: 功能刀具清单 bom添加查询排序与清单明细行删除校验

Created By: @廖丹龙
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1305?initial=true
2024-09-14 14:30:59 +08:00
胡尧
a047ad3bb6 Accept Merge Request #1304: (feature/流程用扫码完成 -> develop)
Merge Request: 去掉多余的js引用,将工件配送页面起点接驳站修改为当前接驳站

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1304?initial=true
2024-09-14 14:09:29 +08:00
胡尧
cc3289c834 将工件配送页面起点接驳站修改为当前接驳站 2024-09-14 13:49:46 +08:00
胡尧
e1093a3c69 去掉多余的js引用 2024-09-14 13:42:38 +08:00
guanhuan
ba955ca658 快速订单上传模型文件检查文件后缀 2024-09-14 08:46:32 +08:00
禹翔辉
946abc5cf7 Accept Merge Request #1303: (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/1303?initial=true
2024-09-13 17:42:16 +08:00
yuxianghui
57ea9ed8f4 Merge branch 'feature/组装单重构_2' into feature/刀具拆解单优化 2024-09-13 17:38:19 +08:00
hujiaying
f8a12b1fe3 优化计划开始时间默认往后延迟10分钟 2024-09-13 17:37:57 +08:00
yuxianghui
722f6257ae 1、优化刀具拆解单 2024-09-13 17:37:20 +08:00
guanhuan
ccbe311c58 快速订单填写的零件图号和零件图纸传给制造订单 2024-09-13 10:45:08 +08:00
管欢
0f6ca0876c Accept Merge Request #1300: (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/1300
2024-09-13 10:41:19 +08:00
马广威
bb60fd0339 Accept Merge Request #1301: (feature/优化制造功能 -> develop)
Merge Request: 优化dashboard接口,及装夹bug

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1301?initial=true
2024-09-13 10:40:58 +08:00
mgw
eefbd5939b Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-09-13 10:38:23 +08:00
mgw
7c01196f17 修复“装夹预调工单没有测量数据时点击左上角的【完成】按钮直接完成工单了” 2024-09-13 10:37:50 +08:00
guanhuan
4c58f4d7f3 快速订单填写的零件图号和零件图纸传给制造订单 2024-09-13 10:22:14 +08:00
胡嘉莹
c741587341 Accept Merge Request #1299: (feature/update_production_line -> 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/1299?initial=true
2024-09-13 10:21:11 +08:00
hujiaying
6ad2fa80f1 修改销售,采购,制造,仓库产品设置产品类型为默认分组 2024-09-13 10:15:26 +08:00
胡尧
55694ae303 Merge branch 'develop' into feature/流程用扫码完成 2024-09-13 08:53:16 +08:00
禹翔辉
8423b12119 Accept Merge Request #1298: (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/1298?initial=true
2024-09-12 17:27:42 +08:00
jinling.yang
5a7ba7f87e Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-12 17:24:43 +08:00
jinling.yang
d36da3df24 Merge branch 'feature/新增消息提醒模版' into develop 2024-09-12 17:24:35 +08:00
杨金灵
302a5f0cf1 Accept Merge Request #1297: (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/1297
2024-09-12 17:24:18 +08:00
yuxianghui
b4fef421dd Merge branch 'feature/功能刀具重构_1' into feature/组装单重构_2 2024-09-12 17:24:13 +08:00
jinling.yang
5f5e991d33 优化页面 2024-09-12 17:24:01 +08:00
yuxianghui
43a49242c5 1、组装单布局调整 2024-09-12 17:21:37 +08:00
yuxianghui
2e5694cd1a 1、优化组装单扫描验证物料方法;2、对组装单物料批次进行调整,删除部分字段和方法;优化获取BOM并自动初始化物料数据方法;3、货位批次模型新增物料字段,新增tree视图,并且除刀柄外物料点击【更多】时跳转至不同的货位批次模型tree视图;4、添加刀柄和其他物料在点击【更多】按钮后选取新物料点击【确认】后回填物料信息到组装单并给出提示信息;5、添加组装物料信息校验功能,初始化或重新选取物料后需进行验证才能进行组装;6、组装单form视图布局调整; 2024-09-12 17:20:58 +08:00
jinling.yang
3c123a07ea Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/新增消息提醒模版 2024-09-12 17:06:33 +08:00
jinling.yang
dec5ddd154 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-12 17:06:24 +08:00
jinling.yang
22864b9669 新增消息提醒模版 2024-09-12 17:06:16 +08:00
管欢
528fa483a2 Accept Merge Request #1296: (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/1296
2024-09-12 16:48:28 +08:00
guanhuan
b5a3815f1f 设备维保标准筛选添加已归档 2024-09-12 16:43:54 +08:00
mgw
1719e0394e 去掉run_time返回 2024-09-12 16:33:55 +08:00
mgw
eae902ee92 调整设备运行时长 2024-09-12 15:36:30 +08:00
liaodanlong
01c57a8691 去除无用代码添加描述信息 2024-09-12 15:17:47 +08:00
liaodanlong
2808005ce9 错误处理 2024-09-12 15:17:09 +08:00
mgw
06c5f65df2 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-09-12 11:08:41 +08:00
mgw
e0bad6ed40 优化当班计划量,完成量数据 2024-09-12 11:08:24 +08:00
jinling.yang
35c8b9316e Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/new 2024-09-12 10:46:40 +08:00
jinling.yang
2ddfbb0dde Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-12 10:46:29 +08:00
guanhuan
988c4a460d 2D加工图纸改为pdf 2024-09-12 10:24:45 +08:00
guanhuan
c050e2f11a 供应商详情的采购员去掉必填 2024-09-12 10:14:38 +08:00
mgw
95e49c7b0f 优化打印输出 2024-09-12 09:17:52 +08:00
管欢
e46a13889e Accept Merge Request #1295: (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/1295
2024-09-12 08:38:01 +08:00
mgw
335151f6ca Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-09-11 18:11:07 +08:00
mgw
908e4a3df6 调整通过数量取值 2024-09-11 18:07:21 +08:00
胡嘉莹
6b403f2c1e Accept Merge Request #1294: (feature/update_production_line -> 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/1294?initial=true
2024-09-11 17:58:51 +08:00
yuxianghui
da5e322229 1、组装单新增五种物料的【更多】按钮,点击按钮跳转至新的弹窗界面,根据功能刀具清单BOM的配置,搜索出全部满足条件的刀柄产品或者其他刀具物料对应的批次、货位,使用tree视图提供选择。2、新增产品tree、search视图 2024-09-11 17:49:43 +08:00
hujiaying
4f9ac16a07 修改计划排程,已取消的不占用小时或日产数量 2024-09-11 17:48:08 +08:00
mgw
4342844845 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-09-11 17:41:49 +08:00
guanhuan
8bc68e1edd 表面工艺参数取消必填 2024-09-11 17:40:28 +08:00
mgw
763d64a1ca 增加设备运行时长效率接口 2024-09-11 14:57:42 +08:00
胡嘉莹
8a10d18f37 Accept Merge Request #1293: (feature/update_production_line -> 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/1293?initial=true
2024-09-11 12:15:17 +08:00
hujiaying
181f490d47 修改计划排程时间范围内判断取值向上取整 2024-09-11 11:53:17 +08:00
mgw
322718d19a Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-09-11 11:45:36 +08:00
mgw
96eeb1cab7 调整检测量、合格量 2024-09-11 11:45:14 +08:00
胡尧
d5c82e4a28 修改代码问题 2024-09-11 10:40:26 +08:00
liaodanlong
436adc5ff6 功能刀具清单 bom添加查询排序与清单明细行删除校验 2024-09-11 10:39:27 +08:00
廖丹龙
97a5d91fce Accept Merge Request #1292: (feature/tax_sync -> develop)
Merge Request: 功能刀具清单 bom添加查询排序与清单明细行删除校验

Created By: @廖丹龙
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1292?initial=true
2024-09-11 10:30:08 +08:00
liaodanlong
9eee8557c2 功能刀具清单 bom添加查询排序与清单明细行删除校验 2024-09-11 10:23:02 +08:00
mgw
4394dd023e 增加交付准时率 2024-09-11 10:11:22 +08:00
胡尧
d39bbe4f99 Accept Merge Request #1291: (feature/流程用扫码完成 -> develop)
Merge Request: 工件配送,解除装夹使用扫码完成

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1291
2024-09-11 09:15:48 +08:00
胡尧
b188c8adfe 修改异常提示 2024-09-11 09:14:00 +08:00
guanhuan
ea6fd42b2e SF快速订单优化 2024-09-11 09:01:24 +08:00
guanhuan
625499f758 SF快速订单优化 2024-09-11 08:59:44 +08:00
guanhuan
0c2d6dd582 SF快速订单优化 2024-09-11 08:52:28 +08:00
mgw
f6a9c97e7f 优化产量部分 2024-09-11 08:51:01 +08:00
yuxianghui
e2d741937e 1、实现功能刀具点击开始组装时,自动获取功能刀具清单BOM信息,并根据规则匹配刀具物料信息进行信息初始化;2、对组装单form界面进行调整,并隐藏夹头物料的所以信息; 2024-09-10 17:52:40 +08:00
胡尧
9a3404f4b8 去掉屏蔽的代码 2024-09-10 17:51:27 +08:00
胡尧
d000aa095f 对于测试用例进行修改 2024-09-10 17:49:52 +08:00
胡尧
8945801db8 解决冲突 2024-09-10 16:16:30 +08:00
胡尧
40f327ea17 修改工件拆卸 2024-09-10 16:13:57 +08:00
mgw
44a2604dbd Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-09-10 16:13:17 +08:00
mgw
0ae7424f04 获取数据按钮增加调用服务功能 2024-09-10 16:12:24 +08:00
胡嘉莹
091af92e8d Accept Merge Request #1290: (feature/update_production_line -> 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/1290
2024-09-10 15:56:16 +08:00
hujiaying
3ed338ed64 修改计划-生产订单列表只显示制造订单 2024-09-10 14:50:24 +08:00
hujiaying
65781570e7 修改计划-生产订单列表只显示制造订单 2024-09-10 14:38:25 +08:00
liaodanlong
e1f7aeaa96 修改字段位置 2024-09-10 13:53:23 +08:00
胡嘉莹
a0a580588c Accept Merge Request #1289: (feature/update_production_line -> 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/1289?initial=true
2024-09-10 11:26:52 +08:00
mgw
a2f2a21a4c Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-09-10 11:17:23 +08:00
hujiaying
1ddb446bc6 修改计划排程单小时产能取值 2024-09-10 11:15:00 +08:00
廖丹龙
dcdc6851b6 Accept Merge Request #1288: (feature/tax_sync -> develop)
Merge Request: bom物料清单添加错误信息,添加刀具物料字段展示

Created By: @廖丹龙
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1288
2024-09-10 09:23:31 +08:00
liaodanlong
ece4e56118 Merge branch 'refs/heads/develop' into feature/tax_sync 2024-09-10 09:19:48 +08:00
liaodanlong
7cd104c047 bom物料清单添加错误信息,添加刀具物料字段展示 2024-09-10 09:18:43 +08:00
yuxianghui
80a7dd75c8 Merge branch 'feature/功能刀具重构' into feature/功能刀具重构_1 2024-09-10 08:28:47 +08:00
hujiaying
ec2ef752b3 补充模块注释 2024-09-09 18:04:10 +08:00
廖丹龙
57d5c57048 Accept Merge Request #1287: (feature/tax_sync -> develop)
Merge Request: bom物料清单,sf与cloud数据同步

Created By: @廖丹龙
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1287
2024-09-09 18:00:55 +08:00
yuxianghui
9b7c22c35e 1、对组装单物料批次号获取逻辑进行优化,更改form视图批次字段显示条件;2、开发 寿命到期功能刀具完成拆解后关联生成并跳转到组装单的需求 需求 2024-09-09 17:54:10 +08:00
liaodanlong
6711f6f6e6 Merge branch 'refs/heads/develop' into feature/tax_sync 2024-09-09 17:37:21 +08:00
liaodanlong
7ac7077f8c bom物料清单 2024-09-09 17:36:23 +08:00
胡尧
ed20249988 Accept Merge Request #1286: (feature/流程用扫码完成 -> develop)
Merge Request: 扫码触发页面按钮,解除装夹起始站点根据下产线终点站点自动赋值

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1286
2024-09-09 17:31:25 +08:00
liaodanlong
dba8d23348 销售订单价格问题 2024-09-09 17:11:33 +08:00
胡嘉莹
ab9062983c Accept Merge Request #1285: (feature/update_production_line -> 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/1285?initial=true
2024-09-09 14:16:31 +08:00
hujiaying
37493a4688 sf计划排程计算排程时间日排程的制造订单总数量不能超过产线日产能数量,计算排程时间制造订单每小时排程不能超过产线小时产能 2024-09-09 14:08:39 +08:00
胡嘉莹
689d149edc Accept Merge Request #1282: (feature/仓库发货下发bfm -> develop)
Merge Request: 修改打印日志

Created By: @胡嘉莹
Reviewed By: @胡尧 @管欢
Approved By: @胡尧 
Accepted By: @胡嘉莹
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1282
2024-09-09 11:02:17 +08:00
管欢
9b9106c5da Accept Merge Request #1283: (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/1283
2024-09-09 10:53:20 +08:00
liaodanlong
9a43af98c3 cloud 与 sf 静态资源同步问题 2024-09-09 10:46:05 +08:00
yuxianghui
1eae92f2b2 1、功能刀具组装单添加物料校验标签字段、新增扫描校验功能、优化相关计算方法;2、添加新旧、粗中精字段值根据所选物料自动计算方法,伸出长自动计算方法;3、去除部分字段的校验方法;4、组装单form、tree视图布局调整,tree视图状态字段添加背景颜色,变更筛选条件; 2024-09-06 17:55:59 +08:00
hujiaying
4560bbc0ed sf计划排程计算生产线是否配置生产线,计算排程时间是否在工作时间内,计算生产线是否有可排程的资源, 2024-09-06 17:48:27 +08:00
胡尧
2d5ef0aae4 解除装夹起点从下产线agv调度任务关联带出 2024-09-06 15:34:30 +08:00
胡尧
096542c8ff 扫码触发按键功能 2024-09-06 09:37:05 +08:00
hujiaying
d123ca5173 修改日有效工作时长计算规则 2024-09-06 09:13:38 +08:00
胡尧
388929f207 扫码触发按键功能 2024-09-05 17:59:55 +08:00
yuxianghui
29b9223a14 1、将功能刀具组装弹窗模型的组装流程以及其中相关的业务逻辑,移动到组装单模型并进行优化;2、功能刀具组装单模型新增部分字段的自动计算方法;新增部分物料字段; 2024-09-05 17:57:23 +08:00
hujiaying
cf283377cd 计划排程考虑CNC生产线日产能的优化需求,新增可用机台数量,单台小时产能,日有效工作时长计算规则 2024-09-05 17:39:36 +08:00
hujiaying
5f218e69d4 计划排程考虑CNC生产线日产能的优化需求,新增可用机台数量,单台小时产能,日有效工作时长计算规则 2024-09-05 17:39:09 +08:00
mgw
7f5fb165de Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-09-05 10:30:21 +08:00
mgw
eec79d7d3e 优化图片返回为url 2024-09-05 10:30:04 +08:00
hujiaying
00809d88f6 修改打印日志 2024-09-05 09:53:32 +08:00
hujiaying
18b0cc6ea0 Merge remote-tracking branch 'origin/develop' into feature/仓库发货下发bfm 2024-09-05 09:38:40 +08:00
hujiaying
54d9902ae4 撤回修改 2024-09-05 09:26:05 +08:00
hujiaying
eff39a39e3 修改 cloud端新建表面工艺参数必填参数不能填写 2024-09-05 09:24:45 +08:00
guanhuan
005a7bb68e 建立设备维保标准,建立设备维保标准修复 2024-09-05 08:51:36 +08:00
胡嘉莹
afc1701150 Accept Merge Request #1281: (feature/仓库发货下发bfm -> 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/1281?initial=true
2024-09-04 18:24:24 +08:00
yuxianghui
72eada0639 1、功能刀具组装tree、form视图布局调整;2、部分字段内容进行调整;3、功能刀具组装单编码自动生成方法添加新逻辑; 2024-09-04 17:55:24 +08:00
jinling.yang
979e8a47ab Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-04 17:45:02 +08:00
jinling.yang
e119a364c1 Merge branch 'feature/优化返工-保留装夹测量数据' into develop 2024-09-04 17:44:50 +08:00
杨金灵
c7b9bbb18d Accept Merge Request #1280: (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/1280
2024-09-04 17:44:07 +08:00
jinling.yang
3cb3dce247 优化返工代码 2024-09-04 17:43:48 +08:00
jinling.yang
a7e1c89e66 还原代码 2024-09-04 17:36:41 +08:00
jinling.yang
ca1060ada5 去掉注释代码 2024-09-04 17:33:54 +08:00
hujiaying
69578ab054 bfm新增库存菜单,代发货菜单,处理sf一次性发货,bfm生成代发货名字规则 2024-09-04 17:28:26 +08:00
jinling.yang
511bed47ff 优化返工-保留装夹测量数据 2024-09-04 16:58:55 +08:00
jinling.yang
26a0412649 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-04 15:00:47 +08:00
杨金灵
4989ff773c Accept Merge Request #1279: (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/1279
2024-09-04 14:59:59 +08:00
jinling.yang
04c0d68763 接口暂时去掉身份验证 2024-09-04 14:58:04 +08:00
hujiaying
589035d42b 处理采购订单代发货数量修改及创建欠单,处理sf发货单下发不需要创建欠单,处理bfm发货单下发内部管理,内部管理处理不需要创建欠单 2024-09-03 19:14:48 +08:00
mgw
5726d50106 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-09-03 17:26:56 +08:00
liaodanlong
6e9c326a52 智能工厂数据同步任务的错误提示优化 2024-09-03 13:56:16 +08:00
hujiaying
fc1cdf4d0e 联调接口数据,处理修改sf下发代发货,bfm接收代发货消息,下发到内部管理,处理采购订单代发货数量修改及创建欠单 2024-09-02 19:02:53 +08:00
杨金灵
45178ac38b Accept Merge Request #1278: (hotfix/修复报废-外协入库验证 -> 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/1278
2024-09-02 17:48:53 +08:00
jinling.yang
09ee1c26b1 修复报废-外协入库验证 2024-09-02 17:33:06 +08:00
liaodanlong
64180ca57f Merge branch 'refs/heads/develop' into feature/tax_sync 2024-09-02 15:43:25 +08:00
jinling.yang
0c052a118c Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-02 14:44:59 +08:00
mgw
cb9fd43f11 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/优化制造功能 2024-09-02 09:04:59 +08:00
mgw
1dc55c2770 调整不良工单逻辑 2024-09-02 09:04:42 +08:00
hujiaying
0ca6d46a7b 修改sf下发欠单,bfm接收下发欠单,及处理发货单的关系 2024-08-31 21:09:56 +08:00
liaodanlong
3624ef4755 销售订单价格问题处理 2024-08-30 16:30:34 +08:00
胡尧
f07a003f2c Accept Merge Request #1277: (hotfix/修改agv回调接口授权 -> develop)
Merge Request: 将agv回调接口授权改为none

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1277?initial=true
2024-08-30 09:46:45 +08:00
jinling.yang
f7f3c005f8 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-30 08:59:09 +08:00
jinling.yang
9a5aa52801 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-08-29 19:45:53 +08:00
hujiaying
1e172cb4e3 处理代发货下发 2024-08-27 17:24:04 +08:00
101 changed files with 3729 additions and 958 deletions

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
from odoo import fields, models, api
class MrpProduction(models.Model):
@@ -12,7 +12,8 @@ class MrpProduction(models.Model):
check_ids = fields.One2many('quality.check', 'production_id', string="Checks")
def _split_productions(self, amounts=False, cancel_remaining_qty=False, set_consumed_qty=False):
productions = super()._split_productions(amounts=amounts, cancel_remaining_qty=cancel_remaining_qty, set_consumed_qty=set_consumed_qty)
productions = super()._split_productions(amounts=amounts, cancel_remaining_qty=cancel_remaining_qty,
set_consumed_qty=set_consumed_qty)
backorders = productions[1:]
if not backorders:
return productions
@@ -20,3 +21,4 @@ class MrpProduction(models.Model):
if wo.current_quality_check_id.component_id:
wo.current_quality_check_id._update_component_quantity()
return productions

View File

@@ -1,41 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Manufacturing Order for Planing view -->
<record id="mrp_production_tree_view_planning" model="ir.ui.view">
<field name="name">mrp.production.tree.inherit.planning</field>
<field name="model">mrp.production</field>
<field name="arch" type="xml">
<tree default_order="date_planned_start asc" decoration-info="state=='confirmed'" decoration-danger="date_planned_start&lt;current_date and state not in ('done','cancel')" decoration-muted="state in ('done','cancel')" string="Manufacturing Orders" name="Production">
<field name="message_needaction" invisible="1"/>
<field name="name"/>
<field name="date_planned_start"/>
<field name="product_id"/>
<field name="product_qty" sum="Total Qty" string="Quantity"/>
<field name="product_uom_id" string="Unit of Measure" options="{'no_open':True,'no_create':True}" groups="uom.group_uom"/>
<field name="reservation_state" string="Availability"/>
<field name="origin"/>
<field name="state"/>
</tree>
</field>
</record>
<!-- Manufacturing Order for Planing view -->
<record id="mrp_production_tree_view_planning" model="ir.ui.view">
<field name="name">mrp.production.tree.inherit.planning</field>
<field name="model">mrp.production</field>
<field name="arch" type="xml">
<tree default_order="date_planned_start asc" decoration-info="state=='confirmed'"
decoration-danger="date_planned_start&lt;current_date and state not in ('done','cancel')"
decoration-muted="state in ('done','cancel')" string="Manufacturing Orders" name="Production">
<field name="message_needaction" invisible="1"/>
<field name="name"/>
<field name="date_planned_start"/>
<field name="product_id"/>
<field name="product_qty" sum="Total Qty" string="Quantity"/>
<field name="product_uom_id" string="Unit of Measure" options="{'no_open':True,'no_create':True}"
groups="uom.group_uom"/>
<field name="reservation_state" string="Availability"/>
<field name="origin"/>
<field name="state"/>
</tree>
</field>
</record>
<!-- <record id="mrp_production_form_inherit_planning" model="ir.ui.view">-->
<!-- <field name="name">mrp.production.form_inherit_planning</field>-->
<!-- <field name="model">mrp.production</field>-->
<!-- <field name="inherit_id" ref="mrp.mrp_production_form_view"/>-->
<!-- <field name="arch" type="xml">-->
<!-- <xpath expr="div[hasclass('oe_chatter')]" position="replace">-->
<!-- &lt;!&ndash; 这里放置替换后的内容 &ndash;&gt;-->
<!-- </xpath>-->
<!-- <xpath expr="//notebook" position="after">-->
<!-- <div class="oe_chatter">-->
<!-- <field name="message_follower_ids"/>-->
<!-- <field name="activity_ids"/>-->
<!-- <field name="message_ids"/>-->
<!-- </div>-->
<!-- </xpath>-->
<!-- </field>-->
<!-- </record>-->
<!-- <record id="mrp_production_form_inherit_planning" model="ir.ui.view">-->
<!-- <field name="name">mrp.production.form_inherit_planning</field>-->
<!-- <field name="model">mrp.production</field>-->
<!-- <field name="inherit_id" ref="mrp.mrp_production_form_view"/>-->
<!-- <field name="arch" type="xml">-->
<!-- <xpath expr="div[hasclass('oe_chatter')]" position="replace">-->
<!-- &lt;!&ndash; 这里放置替换后的内容 &ndash;&gt;-->
<!-- </xpath>-->
<!-- <xpath expr="//notebook" position="after">-->
<!-- <div class="oe_chatter">-->
<!-- <field name="message_follower_ids"/>-->
<!-- <field name="activity_ids"/>-->
<!-- <field name="message_ids"/>-->
<!-- </div>-->
<!-- </xpath>-->
<!-- </field>-->
<!-- </record>-->
<record id="mrp_production_view_search_inherit_planning" model="ir.ui.view">
<field name="name">mrp.production.search.view.inherit.planning</field>
@@ -43,7 +46,9 @@
<field name="inherit_id" ref="mrp.view_mrp_production_filter"/>
<field name="arch" type="xml">
<filter name="filter_planned" position="attributes">
<attribute name="domain">[('is_planned', '=', True), ('date_planned_start', '!=', False), ('date_planned_finished', '!=', False)]</attribute>
<attribute name="domain">[('is_planned', '=', True), ('date_planned_start', '!=', False),
('date_planned_finished', '!=', False)]
</attribute>
</filter>
</field>
</record>
@@ -51,30 +56,33 @@
<record id="production_order_unplan_server_action" model="ir.actions.server">
<field name="name">Unplan orders</field>
<field name="model_id" ref="mrp.model_mrp_production"/>
<field name="binding_model_id" ref="mrp.model_mrp_production" />
<field name="binding_model_id" ref="mrp.model_mrp_production"/>
<field name="binding_view_types">list</field>
<field name="state">code</field>
<field name="code">records.button_unplan()</field>
</record>
<record id="mrp.act_product_mrp_production_workcenter" model="ir.actions.act_window">
<field name="domain">[('bom_id', '!=', False), ('bom_id.operation_ids.workcenter_id', '=', active_id), ('date_planned_start', '!=', False), ('date_planned_finished', '!=', False)]</field>
<field name="domain">[('bom_id', '!=', False), ('bom_id.operation_ids.workcenter_id', '=', active_id),
('date_planned_start', '!=', False), ('date_planned_finished', '!=', False)]
</field>
<field name="view_id" ref="mrp_production_tree_view_planning"/>
</record>
<menuitem id="mrp_workorder_menu_planning"
name="Work Orders"
sequence="2"
parent="mrp.mrp_planning_menu_root"
groups="mrp.group_mrp_routings"/>
name="Work Orders"
sequence="2"
parent="mrp.mrp_planning_menu_root"
groups="mrp.group_mrp_routings"/>
<menuitem id="menu_mrp_workorder_production"
name="Planning by Production"
sequence="1"
action="mrp.action_mrp_workorder_production"
parent="mrp_workorder_menu_planning"/>
name="Planning by Production"
sequence="1"
action="mrp.action_mrp_workorder_production"
parent="mrp_workorder_menu_planning"/>
<menuitem id="menu_mrp_workorder_workcenter"
name="Planning by Workcenter"
sequence="2"
action="mrp_workorder.action_mrp_workorder_dependencies_workcenter"
parent="mrp_workorder_menu_planning"/>
name="Planning by Workcenter"
sequence="2"
action="mrp_workorder.action_mrp_workorder_dependencies_workcenter"
parent="mrp_workorder_menu_planning"/>
</odoo>

View File

@@ -242,3 +242,8 @@ access_sf_fixture_materials_basic_parameters_group_sf_stock_manager,sf_fixture_m
access_sf_multi_mounting_type_group_sf_stock_manager,sf_multi_mounting_type_group_sf_stock_manager,model_sf_multi_mounting_type,sf_base.group_sf_stock_manager,1,0,0,0
access_sf_machine_brand_group_sf_stock_manager,sf_machine_brand_group_sf_stock_manager,model_sf_machine_brand,sf_base.group_sf_stock_manager,1,0,0,0
access_sf_cutting_tool_type_group_sf_stock_manager,sf_cutting_tool_type_group_sf_stock_manager,model_sf_cutting_tool_type,sf_base.group_sf_stock_manager,1,0,0,0
access_sf_cutting_tool_material_group_plan_dispatch,sf_cutting_tool_material_group_plan_dispatch,model_sf_cutting_tool_material,sf_base.group_plan_dispatch,1,0,0,0
access_sf_functional_cutting_tool_model_group_plan_dispatch,sf_functional_cutting_tool_model_group_plan_dispatch,model_sf_functional_cutting_tool_model,sf_base.group_plan_dispatch,1,0,0,0
access_sf_cutting_tool_type_group_plan_dispatch,sf_cutting_tool_type_group_plan_dispatch,model_sf_cutting_tool_type,sf_base.group_plan_dispatch,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
242
243
244
245
246
247
248
249

View File

@@ -0,0 +1,61 @@
/** @odoo-module **/
import { registry } from "@web/core/registry";
import { barcodeGenericHandlers } from '@barcodes/barcode_handlers';
import { patch } from "@web/core/utils/patch";
// 定义新的 clickOnButton 函数
function customClickOnButton(selector) {
console.log("This is the custom clickOnButton function!");
const buttons = document.body.querySelectorAll(selector);
let length = buttons.length;
if (length > 0) {
buttons[length - 1].click();
} else {
console.warn(`Button with selector ${selector} not found`);
}
}
patch(barcodeGenericHandlers, "start", {
start(env, { ui, barcode, notification }) {
// 使用新定义的 clickOnButton 函数
const COMMANDS = {
"O-CMD.EDIT": () => customClickOnButton(".o_form_button_edit"),
"O-CMD.DISCARD": () => customClickOnButton(".o_form_button_cancel"),
"O-CMD.SAVE": () => customClickOnButton(".o_form_button_save"),
"O-CMD.PREV": () => customClickOnButton(".o_pager_previous"),
"O-CMD.NEXT": () => customClickOnButton(".o_pager_next"),
"O-CMD.PAGER-FIRST": () => updatePager("first"),
"O-CMD.PAGER-LAST": () => updatePager("last"),
"O-CMD.CONFIRM": () => customClickOnButton(".jikimo_button_confirm"),
};
barcode.bus.addEventListener("barcode_scanned", (ev) => {
const barcode = ev.detail.barcode;
if (barcode.startsWith("O-BTN.")) {
let targets = [];
try {
targets = getVisibleElements(ui.activeElement, `[barcode_trigger=${barcode.slice(6)}]`);
} catch (_e) {
console.warn(`Barcode '${barcode}' is not valid`);
}
for (let elem of targets) {
elem.click();
}
}
if (barcode.startsWith("O-CMD.")) {
const fn = COMMANDS[barcode];
if (fn) {
fn();
} else {
notification.add(env._t("Barcode: ") + `'${barcode}'`, {
title: env._t("Unknown barcode command"),
type: "danger"
});
}
}
});
}
})

View File

@@ -5,9 +5,12 @@ import { registry } from '@web/core/registry';
import { formView } from '@web/views/form/form_view';
import { FormController } from '@web/views/form/form_controller';
import { listView } from '@web/views/list/list_view';
import { ListController } from '@web/views/list/list_controller'
import { onRendered, onMounted } from "@odoo/owl";
export class RemoveFocusController extends FormController {
export class RemoveFocusFormController extends FormController {
setup() {
super.setup();
@@ -17,7 +20,23 @@ export class RemoveFocusController extends FormController {
}
}
registry.category('views').add('remove_focus_view', {
registry.category('views').add('remove_focus_form_view', {
...formView,
Controller: RemoveFocusController,
Controller: RemoveFocusFormController,
});
export class RemoveFocusListController extends ListController {
setup() {
super.setup();
onMounted(() => {
this.__owl__.bdom.el.querySelectorAll(':focus').forEach(element => element.blur());
})
}
}
registry.category('views').add('remove_focus_list_view', {
...listView,
Controller: RemoveFocusListController,
});

View File

@@ -12,7 +12,6 @@
</tree>
</field>
</record>
<record model="ir.ui.view" id="mrs_production_process_parameter_form">
<field name="model">sf.production.process.parameter</field>
<field name="arch" type="xml">
@@ -26,19 +25,19 @@
<group>
<group>
<field name="code" readonly="1"/>
<field name="process_id" readonly="1"/>
<field name="process_description" readonly="1"/>
<field name="process_id" attrs="{'readonly': [('code', '!=', False)]}"/>
<field name="process_description" attrs="{'readonly': [('code', '!=', False)]}"/>
<field name="gain_way"/>
</group>
<group>
<field name="processing_day" readonly="1"/>
<field name="travel_day" readonly="1"/>
<field name="processing_mm" readonly="1"/>
<field name="processing_day" attrs="{'readonly': [('code', '!=', False)]}"/>
<field name="travel_day" attrs="{'readonly': [('code', '!=', False)]}"/>
<field name="processing_mm" attrs="{'readonly': [('code', '!=', False)]}"/>
</group>
</group>
<notebook>
<page string="适用材料">
<field name="materials_model_ids" readonly="1"></field>
<field name="materials_model_ids" attrs="{'readonly': [('code', '!=', False)]}"></field>
</page>
</notebook>
</sheet>

View File

@@ -80,10 +80,10 @@
<field name="name">sf.cutter.function.tree</field>
<field name="model">sf.functional.cutting.tool.model</field>
<field name="arch" type="xml">
<tree string="功能刀具类型" create="0" delete="0" edit="1">
<field name="name" string="名称"/>
<field name="code"/>
<field name="remark"/>
<tree string="功能刀具类型" create="0" delete="0" edit="1" editable="bottom">
<field name="name" string="名称" readonly="1"/>
<field name="code" readonly="1"/>
<field name="remark" readonly="1"/>
</tree>
</field>
</record>

View File

@@ -1,3 +1,4 @@
import traceback
from datetime import datetime
import logging
import requests
@@ -53,11 +54,14 @@ class StatusChange(models.Model):
if not ret.get('error'):
logging.info('接口已经执行=============')
else:
logging.error('工厂加工同步订单状态失败 {}'.format(ret.text))
raise UserError('工厂加工同步订单状态失败')
traceback_error = traceback.format_exc()
logging.error("bfm订单状态同步失败:%s request info %s" % traceback_error)
logging.error('/api/get/state/get_order 请求失败{}'.format(ret))
raise UserError('工厂加工同步订单状态到bfm失败')
except UserError as e:
logging.error('工厂加工同步订单状态失败 {}'.format(e))
raise UserError('工厂加工同步订单状态失败')
traceback_error = traceback.format_exc()
logging.error("工厂加工同步订单状态失败:%s " % traceback_error)
raise UserError(e)
return res
def action_cancel(self):

View File

@@ -1,6 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record id="mrp.product_template_action" model="ir.actions.act_window">
<field name="context">
{"search_default_categ_id":1,"search_default_consumable": 1, 'default_detailed_type': 'product'}
</field>
</record>
<record model="ir.ui.view" id="view_sale_product_template_form_inherit_sf">
<field name="name">product.template.form.inherit.sf</field>
<field name="model">product.template</field>
@@ -88,7 +93,8 @@
</div>
<field name="model_volume" string="体积[mm³]"/>
<field name="product_model_type_id" string="模型类型"/>
<field name="model_processing_panel" placeholder="例如R,U" string="加工面板" readonly="1"/>
<field name="model_processing_panel" placeholder="例如R,U" string="加工面板"
readonly="1"/>
<field name="model_machining_precision"/>
<field name="model_process_parameters_ids" string="表面工艺参数"
widget="many2many_tags"
@@ -103,11 +109,11 @@
'刀具')], 'required': True}
</attribute>
</xpath>
<!-- <xpath expr="//field[@name='default_code']" position="attributes">-->
<!-- <attribute name="attrs">{'readonly': [('categ_type', '=', '刀具')], 'invisible':-->
<!-- [('product_variant_count', '>' , 1)]}-->
<!-- </attribute>-->
<!-- </xpath>-->
<!-- <xpath expr="//field[@name='default_code']" position="attributes">-->
<!-- <attribute name="attrs">{'readonly': [('categ_type', '=', '刀具')], 'invisible':-->
<!-- [('product_variant_count', '>' , 1)]}-->
<!-- </attribute>-->
<!-- </xpath>-->
</field>
</record>

View File

@@ -10,6 +10,8 @@
'depends': ['hr'],
'data': [
'views/hr_employee.xml',
'views/res_config_settings_views.xml',
'data/cron_data.xml',
],
'demo': [
],

15
sf_hr/data/cron_data.xml Normal file
View File

@@ -0,0 +1,15 @@
<odoo>
<data noupdate="1">
<record model="ir.cron" id="ir_cron_employee_info_sync">
<field name="name">员工企微id同步</field>
<field name="model_id" ref="hr.model_hr_employee"/>
<field name="state">code</field>
<field name="code">model._employee_info_sync()</field>
<field name="interval_number">1</field>
<field name="interval_type">days</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
</record>
</data>
</odoo>

View File

@@ -1,2 +1,4 @@
# -*- coding: utf-8 -*-
from . import hr_employee
from . import res_config_setting

View File

@@ -0,0 +1,26 @@
import logging
import requests
from odoo import models, fields, api, _
_logger = logging.getLogger(__name__)
class JkmPracticeEmployee(models.Model):
_inherit = 'hr.employee'
_description = '员工信息'
we_id = fields.Char(string='企微ID', index=True)
def _employee_info_sync(self):
url = '/api/get/organization'
config = self.env['res.config.settings'].get_values()
ret = requests.post((config['ims_url'] + url), json={}, data={})
result = ret.json()['result']
if result['code'] == 200:
if result['employee_list']:
for employee_info in result['employee_list']:
if employee_info['work_email']:
self.sudo().search([('work_email', '=', employee_info['work_email'])]).write(
{'we_id': employee_info['we_id']})
else:
logging.info('_employee_info_sync error:%s' % result['message'])

View File

@@ -0,0 +1,30 @@
# -*- coding: utf-8 -*-
import logging
from odoo import api, fields, models
_logger = logging.getLogger(__name__)
class ResIMSConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
ims_url = fields.Char('综合管理系统访问地址')
@api.model
def get_values(self):
"""
重载获取参数的方法,参数都存在系统参数中
:return:
"""
values = super(ResIMSConfigSettings, self).get_values()
config = self.env['ir.config_parameter'].sudo()
ims_url = config.get_param('ims_url', default='')
values.update(
ims_url=ims_url,
)
return values
def set_values(self):
super(ResIMSConfigSettings, self).set_values()
ir_config = self.env['ir.config_parameter'].sudo()
ir_config.set_param("ims_url", self.ims_url or "")

View File

@@ -7,7 +7,16 @@
<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>
<attribute name="required">1</attribute>
</xpath>
<xpath expr="//page[@name='public']" position='after'>
<page string="企业微信">
<group col="2">
<group>
<field name="we_id"/>
</group>
</group>
</page>
</xpath>
</field>
</record>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="res_config_settings_finance_view_form_extend" model="ir.ui.view">
<field name="name">res.config.settings.finance.view.form.extend</field>
<field name="model">res.config.settings</field>
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[hasclass('app_settings_block')]/div" position="before">
<div>
<h2>综合管理系统接口配置</h2>
<div class="row mt16 o_settings_container" id="jd_api">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
<div class="text-muted">
<label for="ims_url"/>
<field name="ims_url"/>
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@@ -6,7 +6,7 @@ import base64
import logging
import psycopg2
from datetime import datetime, timedelta
from odoo import http
from odoo import http, fields
from odoo.http import request
# 数据库连接配置
@@ -15,14 +15,18 @@ db_config = {
"user": "postgres",
"password": "postgres",
"port": "5432",
"host": "172.16.10.98"
"host": "172.16.10.113"
}
def convert_to_seconds(time_str):
# 修改正则表达式,使 H、M、S 部分可选
if time_str is None:
return 0
pattern = r"(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?"
match = re.match(pattern, time_str)
if match:
@@ -66,7 +70,7 @@ class Sf_Dashboard_Connect(http.Controller):
# 获取当前时间的时间戳
current_timestamp = datetime.now().timestamp()
print(current_timestamp)
# print(current_timestamp)
# tem_list = [
# "XT-GNJC-WZZX-X800-Y550-Z550-T24-A5-1", "XT-GNJC-LSZX-X800-Y550-Z550-T24-A3-3",
@@ -162,7 +166,8 @@ class Sf_Dashboard_Connect(http.Controller):
'first_online_duration': first_online_duration,
# 停机时间:关机时间 - 运行时间
# 停机时长:关机时间 - 初次上线时间
'img': f'data:image/png;base64,{machine_data.machine_tool_picture.decode("utf-8")}',
# 'img': f'data:image/png;base64,{machine_data.machine_tool_picture.decode("utf-8")}',
'img': f'https://xt.sf.jikimo.com/equipment/get_image/{machine_data.id}',
'equipment_type': machine_data.category_id.name,
})
@@ -194,7 +199,7 @@ class Sf_Dashboard_Connect(http.Controller):
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)
# print('begin_time: %s' % begin_time)
for item in machine_list:
sql = '''
@@ -275,7 +280,7 @@ class Sf_Dashboard_Connect(http.Controller):
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)
# print('line_list: %s' % line_list)
res['LineList'] = line_list
except Exception as e:
@@ -300,21 +305,40 @@ class Sf_Dashboard_Connect(http.Controller):
try:
plan_obj = request.env['sf.production.plan'].sudo()
production_obj = request.env['mrp.production'].sudo()
line_list = ast.literal_eval(kw['line_list'])
print('line_list: %s' % 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_total_counts = production_obj.search_count(
[('production_line_id.name', '=', line), ('state', 'not in', ['cancel']),
('active', '=', True)])
# 工单完成量
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_plan_counts = plan_obj.search_count(
# [('production_line_id.name', '=', line), ('state', 'not in', ['finished'])])
# 查找符合条件的生产计划记录
plan_data = plan_obj.search([
('production_line_id.name', '=', line),
])
# 过滤出那些检测结果状态为 '返工' 或 '报废' 的记录
faulty_plans = plan_data.filtered(lambda p: any(
result.test_results in ['返工', '报废'] for result in p.production_id.detection_result_ids
))
# 查找制造订单取消与归档的数量
cancel_order_count = production_obj.search_count(
[('production_line_id.name', '=', line), ('state', 'in', ['cancel']),
('active', '=', False)])
# 计算符合条件的记录数量
# plan_data_fault_counts = len(faulty_plans) + cancel_order_count
plan_data_fault_counts = len(faulty_plans)
# 工单返工数量
@@ -326,17 +350,76 @@ class Sf_Dashboard_Connect(http.Controller):
(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
plan_data_progress_deviation = plan_data_total_counts - plan_data_finish_counts - plan_data_fault_counts
# 完成记录
plan_data_finish_orders = plan_obj.search(
[('production_line_id.name', '=', line), ('state', 'in', ['finished'])])
# # 检测量
# detection_nums = 0
# for i in plan_data_finish_orders:
# print('i: %s' % i)
# if i.production_id.detection_result_ids:
# detection_nums += 1
#
# print('detection_nums: %s' % detection_nums)
# 检测量
detection_data = len(
plan_data_finish_orders.mapped('production_id.detection_result_ids').filtered(lambda r: r))
# 检测合格量
pass_nums = plan_data_finish_orders.filtered(lambda p: any(
result.test_results in ['合格'] for result in p.production_id.detection_result_ids
))
# 质量合格率
pass_rate = 1
if pass_nums:
pass_rate = round(
(len(pass_nums) / detection_data if len(plan_data_finish_orders) > 0 else 0), 3)
# 返工率
rework_rate = round(
(plan_data_rework_counts / plan_data_finish_counts if plan_data_finish_counts > 0 else 0), 3)
# 交付准时率
delay_num = 0
for plan in plan_data_finish_orders:
sale_obj = request.env['sale.order'].sudo().search([('name', '=', plan.origin)])
if sale_obj:
if sale_obj.deadline_of_delivery and plan.actual_end_time:
# 将 deadline_of_delivery 转换为字符串
date_as_string = sale_obj.deadline_of_delivery.strftime('%Y-%m-%d')
# 然后使用 strptime 将字符串转换为 datetime 对象
date_as_datetime = datetime.strptime(date_as_string, '%Y-%m-%d')
# 将 actual_end_time 转换为 datetime 对象
datetime_value = fields.Datetime.from_string(plan.actual_end_time)
# 给 datetime_value 加1天
new_datetime_value = datetime_value + timedelta(days=1)
# 比较 new_datetime_value 和 date_as_datetime 的大小
if new_datetime_value.date() > date_as_datetime.date():
delay_num += 1
delay_rate = round((delay_num / plan_data_finish_counts if plan_data_finish_counts > 0 else 0), 3)
on_time_rate = 1 - delay_rate
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_plan_counts': plan_data_total_counts,
'plan_data_fault_counts': plan_data_fault_counts,
'nopass_orders_counts': detection_data - len(pass_nums),
'finishe_rate': finishe_rate,
'plan_data_progress_deviation': plan_data_progress_deviation,
'plan_data_rework_counts': plan_data_rework_counts
'plan_data_rework_counts': plan_data_rework_counts,
'on_time_rate': on_time_rate,
'detection_data': detection_data,
'pass_rate': pass_rate
}
res['data'][line] = data
@@ -363,7 +446,28 @@ class Sf_Dashboard_Connect(http.Controller):
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)
# print('line_list: %s' % line_list)
print('kw', kw)
time_unit = kw.get('time_unit', 'day').strip('"') # 默认单位为天
print('time_unit: %s' % time_unit)
# 日期或小时循环生成器根据time_unit决定是按天还是按小时
def get_time_intervals(start_time, end_time, time_unit):
intervals = []
current_time = start_time
if time_unit == 'hour':
delta = timedelta(hours=1)
else:
delta = timedelta(days=1)
while current_time < end_time:
next_time = current_time + delta
# 确保最后一个时间段不会超出end_time
if next_time > end_time:
next_time = end_time
intervals.append((current_time, next_time))
current_time = next_time
return intervals
def get_date_list(start_date, end_date):
date_list = []
@@ -374,10 +478,33 @@ class Sf_Dashboard_Connect(http.Controller):
return date_list
for line in line_list:
date_list = get_date_list(begin_time, end_time)
date_field_name = 'actual_end_time' # 替换为你模型中的实际字段名
order_counts = []
date_field_name = 'actual_end_time' # 替换为你模型中的实际字段名
if time_unit == 'hour':
time_intervals = get_time_intervals(begin_time, end_time, time_unit)
print('============================= %s' % time_intervals)
time_count_dict = {}
for time_interval in time_intervals:
start_time, end_time = time_interval
# print(start_time, end_time)
orders = plan_obj.search([('production_line_id.name', '=', line), ('state', 'in', ['finished']),
(date_field_name, '>=', start_time.strftime('%Y-%m-%d 00:00:00')),
(date_field_name, '<', end_time.strftime('%Y-%m-%d 00:00:00'))
])
# 使用小时和分钟作为键,确保每个小时的数据有独立的键
key = start_time.strftime('%H:%M:%S') # 只取小时:分钟:秒作为键
time_count_dict[key] = len(orders)
# order_counts.append()
res['data'][line] = {
'finish_order_nums': time_count_dict,
'plan_order_nums': 28
}
return json.dumps(res)
date_list = get_date_list(begin_time, end_time)
for date in date_list:
next_day = date + timedelta(days=1)
@@ -487,7 +614,7 @@ class Sf_Dashboard_Connect(http.Controller):
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)
# print('line_list: %s' % line_list)
not_done_data = []
done_data = []
final_data = {}
@@ -495,11 +622,16 @@ class Sf_Dashboard_Connect(http.Controller):
for line in line_list:
# 未完成订单
not_done_orders = plan_obj.search(
[('production_line_id.name', '=', line), ('state', 'not in', ['finished'])])
print(not_done_orders)
[('production_line_id.name', '=', line), ('state', 'not in', ['finished']),
('production_id.state', 'not in', ['cancel']), ('active', '=', True)
])
# print(not_done_orders)
# 完成订单
finish_orders = plan_obj.search([('production_line_id.name', '=', line), ('state', 'in', ['finished'])])
print(finish_orders)
finish_orders = plan_obj.search([
('production_line_id.name', '=', line), ('state', 'in', ['finished']),
('production_id.state', 'not in', ['cancel']), ('active', '=', True)
])
# print(finish_orders)
# 获取所有未完成订单的ID列表
order_ids = [order.id for order in not_done_orders]
@@ -622,11 +754,11 @@ class Sf_Dashboard_Connect(http.Controller):
# 执行SQL命令
cur.execute(sql, (item,))
result = cur.fetchall()
print('result========', result)
# print('result========', result)
cur.execute(sql2, (item,))
result2 = cur.fetchall()
print('result2========', result2)
# print('result2========', result2)
#
for row in result:
res['data'][item] = {'idle_count': row[0]}
@@ -682,7 +814,7 @@ class Sf_Dashboard_Connect(http.Controller):
# 执行SQL命令
cur.execute(sql)
result = cur.fetchall()
print('result', result)
# print('result', result)
# 将查询结果转换为字典列表
data = []
@@ -830,3 +962,222 @@ class Sf_Dashboard_Connect(http.Controller):
# 返回数据
res['data'] = oee_data
return json.dumps(res)
@http.route(['/equipment/get_image/<int:record_id>'], type='http', auth="public", website=True)
def get_image(self, record_id, **kwargs):
# 获取模型中的记录
record = request.env['maintenance.equipment'].sudo().browse(record_id)
# 获取图片字段的数据
image_data_base64 = record.machine_tool_picture
if image_data_base64:
# 将Base64解码为二进制数据
image_data_binary = base64.b64decode(image_data_base64)
# 返回图片数据并设置正确的Content-Type
return request.make_response(image_data_binary, headers=[('Content-Type', 'image/png')])
else:
# 如果没有图片数据返回404
return request.not_found()
# 设备运行率
@http.route('/api/RunningTime', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
def RunningTime(self, **kw):
"""
获取设备运行时长
"""
res = {'status': 1, 'message': '成功', 'data': {}}
# 连接数据库
conn = psycopg2.connect(**db_config)
# 获取请求的机床数据
machine_list = ast.literal_eval(kw['machine_list'])
def fetch_result_as_dict(cursor):
"""辅助函数:将查询结果转为字典"""
columns = [desc[0] for desc in cursor.description]
return dict(zip(columns, cursor.fetchone())) if cursor.rowcount != 0 else None
# 初始化当天、当月和有记录以来的总时长
day_total_running_time = 0
day_total_process_time = 0
day_work_rate = 0
month_total_running_time = 0
month_total_process_time = 0
month_work_rate = 0
all_time_total_running_time = 0
all_time_total_process_time = 0
all_time_work_rate = 0
for item in machine_list:
# 获取当天第一条记录排除device_state等于离线的记录
with conn.cursor() as cur:
cur.execute("""
SELECT * FROM device_data
WHERE device_name = %s
AND time::date = CURRENT_DATE
AND device_state != '离线'
ORDER BY time ASC
LIMIT 1;
""", (item,))
first_today = fetch_result_as_dict(cur)
# print("当天第一条记录(非离线):", first_today)
# 获取当天最新一条记录排除device_state等于离线的记录
with conn.cursor() as cur:
cur.execute("""
SELECT * FROM device_data
WHERE device_name = %s
AND time::date = CURRENT_DATE
AND device_state != '离线'
ORDER BY time DESC
LIMIT 1;
""", (item,))
last_today = fetch_result_as_dict(cur)
# print("当天最新一条记录(非离线):", last_today)
# 计算当天运行时长
if first_today and last_today:
running_time = convert_to_seconds(last_today['run_time']) - convert_to_seconds(first_today['run_time'])
process_time = convert_to_seconds(last_today['process_time']) - convert_to_seconds(
first_today['process_time'])
day_total_running_time += abs(running_time)
day_total_process_time += abs(process_time)
# 获取当月第一条记录排除device_state等于离线的记录
with conn.cursor() as cur:
cur.execute("""
SELECT * FROM device_data
WHERE device_name = %s
AND EXTRACT(YEAR FROM time) = EXTRACT(YEAR FROM CURRENT_DATE)
AND EXTRACT(MONTH FROM time) = EXTRACT(MONTH FROM CURRENT_DATE)
AND device_state != '离线'
ORDER BY time ASC
LIMIT 1;
""", (item,))
first_month = fetch_result_as_dict(cur)
# print("当月第一条记录(非离线):", first_month)
# 获取当月最新一条记录排除device_state等于离线的记录
with conn.cursor() as cur:
cur.execute("""
SELECT * FROM device_data
WHERE device_name = %s
AND EXTRACT(YEAR FROM time) = EXTRACT(YEAR FROM CURRENT_DATE)
AND EXTRACT(MONTH FROM time) = EXTRACT(MONTH FROM CURRENT_DATE)
AND device_state != '离线'
ORDER BY time DESC
LIMIT 1;
""", (item,))
last_month = fetch_result_as_dict(cur)
# print("当月最新一条记录(非离线):", last_month)
# 计算当月运行时长
if first_month and last_month:
month_running_time = convert_to_seconds(last_month['run_time']) - convert_to_seconds(
first_month['run_time'])
month_process_time = convert_to_seconds(last_month['process_time']) - convert_to_seconds(
first_month['process_time'])
month_total_running_time += abs(month_running_time)
month_total_process_time += abs(month_process_time)
# 获取有记录以来的第一条记录排除device_state等于离线的记录
with conn.cursor() as cur:
cur.execute("""
SELECT * FROM device_data
WHERE device_name = %s
AND device_state != '离线'
ORDER BY time ASC
LIMIT 1;
""", (item,))
first_all_time = fetch_result_as_dict(cur)
# print("有记录以来的第一条记录(非离线):", first_all_time)
# 获取有记录以来的最新一条记录排除device_state等于离线的记录
with conn.cursor() as cur:
cur.execute("""
SELECT * FROM device_data
WHERE device_name = %s
AND device_state != '离线'
ORDER BY time DESC
LIMIT 1;
""", (item,))
last_all_time = fetch_result_as_dict(cur)
# print("有记录以来的最新一条记录(非离线):", last_all_time)
# 计算有记录以来的运行时长
if first_all_time and last_all_time:
all_time_running_time = convert_to_seconds(last_all_time['run_time']) - convert_to_seconds(
first_all_time['run_time'])
all_time_process_time = convert_to_seconds(last_all_time['process_time']) - convert_to_seconds(
first_all_time['process_time'])
all_time_total_running_time += abs(all_time_running_time)
all_time_total_process_time += abs(all_time_process_time)
# 计算当天工作效率
if day_total_running_time > day_total_process_time:
day_work_rate = day_total_process_time / day_total_running_time if day_total_running_time != 0 else 0
else:
day_work_rate = day_total_running_time / day_total_process_time if day_total_process_time != 0 else 0
print("当天工作效率: %s" % day_work_rate)
# 计算当月工作效率
if month_total_running_time > month_total_process_time:
month_work_rate = month_total_process_time / month_total_running_time if month_total_running_time != 0 else 0
else:
month_work_rate = month_total_running_time / month_total_process_time if month_total_process_time != 0 else 0
print("当月工作效率: %s" % month_work_rate)
# 计算有记录以来的工作效率
if all_time_total_running_time > all_time_total_process_time:
all_time_work_rate = all_time_total_process_time / all_time_total_running_time if all_time_total_running_time != 0 else 0
else:
all_time_work_rate = all_time_total_running_time / all_time_total_process_time if all_time_total_process_time != 0 else 0
print("有记录以来的工作效率: %s" % all_time_work_rate)
conn.close()
# 返回数据
res['data']['day_work_rate'] = day_work_rate
res['data']['month_work_rate'] = month_work_rate
res['data']['all_time_work_rate'] = all_time_work_rate
return json.dumps(res)
# 设备运行时长
@http.route('/api/RunningTimeDetail', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
def RunningTimeDetail(self, **kw):
"""
获取
"""
res = {'status': 1, 'message': '成功', 'data': {}}
# 连接数据库
conn = psycopg2.connect(**db_config)
# 获取请求的机床数据
machine_list = ast.literal_eval(kw['machine_list'])
def fetch_result_as_dict(cursor):
"""辅助函数:将查询结果转为字典"""
columns = [desc[0] for desc in cursor.description]
return dict(zip(columns, cursor.fetchone())) if cursor.rowcount != 0 else None
for item in machine_list:
with conn.cursor() as cur:
cur.execute("""
SELECT * FROM device_data
WHERE device_name = %s
AND device_state != '离线'
ORDER BY time DESC
LIMIT 1;
""", (item,))
last_all_time = fetch_result_as_dict(cur)
# 返回数据
res['data'][item] = {
'wait_time': last_all_time['run_time'] if last_all_time['run_time'] is not None else 0,
'cut_time': last_all_time['process_time'] if last_all_time['process_time'] is not None else 0,
'power_on_time': last_all_time['power_on_time'] if last_all_time['power_on_time'] is not None else 0
}
conn.close()
return json.dumps(res)

View File

@@ -16,6 +16,7 @@ class ResBFMConfigSettings(models.TransientModel):
# ("https://bfm.jikimo.com", "正式环境(https://bfm.jikimo.com)")], string='bfm环境', store=True)
bfm_url_new = fields.Char('业务平台环境路径', placeholder='请输入当前对应的业务平台环境路径')
get_check_file_path = fields.Char('获取检查文件路径', default='')
@api.model
def get_values(self):
@@ -26,9 +27,11 @@ class ResBFMConfigSettings(models.TransientModel):
values = super(ResBFMConfigSettings, self).get_values()
config = self.env['ir.config_parameter'].sudo()
bfm_url_new = config.get_param('bfm_url_new', default='')
get_check_file_path = config.get_param('get_check_file_path', default='')
values.update(
bfm_url_new=bfm_url_new,
get_check_file_path=get_check_file_path
)
return values
@@ -36,3 +39,4 @@ class ResBFMConfigSettings(models.TransientModel):
super(ResBFMConfigSettings, self).set_values()
ir_config = self.env['ir.config_parameter'].sudo()
ir_config.set_param("bfm_url_new", self.bfm_url_new or "")
ir_config.set_param("get_check_file_path", self.get_check_file_path or "")

View File

@@ -22,6 +22,33 @@
</div>
</div>
</div>
<div>
<h2>获取检测报告服务配置</h2>
<div class="row mt16 o_settings_container" id="check_report_config">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
<div class="text-muted">
<label for="get_check_file_path" />
<field name="get_check_file_path" string="检测报告服务地址"/>
</div>
</div>
<!-- </div> -->
</div>
</div>
</div>
</xpath>
<xpath expr="//div[@id='check_report_config']/div" position="after">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane">
<field name="is_get_detection_file"/>
</div>
<div class="o_setting_right_pane">
<div class="text-muted">
<label for="is_get_detection_file"/>
</div>
</div>
</div>
</xpath>
</field>
</record>

View File

@@ -24,6 +24,7 @@ class SfEquipmentSaintenanceStandards(models.Model):
remark = fields.Char('备注')
maintenance_type = fields.Selection([('保养', '保养'), ("检修", "检修")], string='类型', default='保养')
name = fields.Char(string='名称')
active = fields.Boolean(default=True)
@api.model_create_multi
def create(self, vals_list):

View File

@@ -38,8 +38,7 @@ class SfMaintenanceEquipment(models.Model):
crea_url = "/api/machine_tool/create"
#AGV运行日志
# AGV运行日志
agv_logs = fields.One2many('maintenance.equipment.agv.log', 'equipment_id', string='AGV运行日志')
# 1212修改后的字段
number_of_axles = fields.Selection(
@@ -117,7 +116,6 @@ class SfMaintenanceEquipment(models.Model):
# num = "%04d" % m
# return num
equipment_maintenance_standards_ids = fields.Many2many('equipment.maintenance.standards',
'sf_maintenance_equipment_ids', string='设备维保标准')
eq_maintenance_id = fields.Many2one('equipment.maintenance.standards', string='设备保养标准',
@@ -179,7 +177,8 @@ class SfMaintenanceEquipment(models.Model):
type_id = fields.Many2one('sf.machine_tool.type', '型号')
state = fields.Selection(
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"), ("封存(报废)", "封存(报废)")],
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"),
("封存(报废)", "封存(报废)")],
default='正常', string="机床状态")
run_time = fields.Char('总运行时长')
# 0606新增字段
@@ -328,7 +327,7 @@ class SfMaintenanceEquipment(models.Model):
item.tool_diameter_min = item.type_id.tool_diameter_min
item.machine_tool_category = item.type_id.machine_tool_category.id
item.brand_id = item.type_id.brand_id.id
#新增修改字段
# 新增修改字段
item.taper_type_id = item.type_id.taper_type_id.id
item.function_type = item.type_id.function_type
item.a_axis = item.type_id.a_axis
@@ -370,7 +369,6 @@ class SfMaintenanceEquipment(models.Model):
item.image_id = item.type_id.jg_image_id.ids
item.image_lq_id = item.type_id.lq_image_id.ids
# AGV小车设备参数
AGV_L = fields.Char('AGV尺寸(长)')
AGV_W = fields.Char('AGV尺寸(宽)')
@@ -461,18 +459,6 @@ class SfMaintenanceEquipment(models.Model):
original_value = fields.Char('原值')
incomplete_value = fields.Char('残值')
# 注册同步机床
def enroll_machine_tool(self):
sf_sync_config = self.env['res.config.settings'].get_values()
@@ -763,7 +749,7 @@ class SfMaintenanceEquipment(models.Model):
image_id = fields.Many2many('maintenance.equipment.image', 'equipment_id',
domain="[('type', '=', '加工能力')]")
image_lq_id = fields.Many2many('maintenance.equipment.image', 'equipment_lq_id', string='冷却方式',
domain="[('type', '=', '冷却方式')]")
domain="[('type', '=', '冷却方式')]")
class SfRobotAxisNum(models.Model):
@@ -777,4 +763,5 @@ class SfRobotAxisNum(models.Model):
weight = fields.Char('最大负载(kg)')
permissible_load_torque = fields.Char('允许负载扭矩(N-m)')
permissible_inertial_torque = fields.Char('允许惯性扭矩(kg-m²)')
equipment_id = fields.Many2one('maintenance.equipment', string='机器人', domain="[('equipment_type', '=', '机器人')]")
equipment_id = fields.Many2one('maintenance.equipment', string='机器人',
domain="[('equipment_type', '=', '机器人')]")

View File

@@ -6,15 +6,16 @@
<field name="name">equipment.maintenance.standards.form</field>
<field name="model">equipment.maintenance.standards</field>
<field name="arch" type="xml">
<form string="设备维保标准">
<form string="设备维保标准" delete="false" duplicate="false">
<sheet>
<group>
<group>
<field name="code" readonly="1" force_save="1"/>
<field name="name" readonly="1" force_save="1"/>
<field name="maintenance_equipment_category_id" required="1" />
<field name="eq_maintenance_ids" invisible='1'/>
<field name="overhaul_ids" invisible='1'/>
<group>
<field name="active" invisible="1"/>
<field name="code" readonly="1" force_save="1"/>
<field name="name" readonly="1" force_save="1"/>
<field name="maintenance_equipment_category_id" required="1"/>
<field name="eq_maintenance_ids" invisible='1'/>
<field name="overhaul_ids" invisible='1'/>
</group>
@@ -50,7 +51,8 @@
<field name="name">equipment.maintenance.standards.tree</field>
<field name="model">equipment.maintenance.standards</field>
<field name="arch" type="xml">
<tree string="设备维保标准">
<tree string="设备维保标准" delete="false">
<field name="active" invisible="1"/>
<field name="code" readonly="1" force_save="1"/>
<field name="maintenance_type" required="1"/>
<field name="name" required="1"/>
@@ -77,6 +79,7 @@
<field name="name" string="日常机床保养"/>
<field name="created_user_id" string="创建人"/>
<field name="maintenance_equipment_category_id" string="设备类别"/>
<filter name="filter_active" string="已归档" domain="[('active','=',False)]"/>
</search>
</field>
</record>

View File

@@ -477,7 +477,7 @@ class Manufacturing_Connect(http.Controller):
logging.info('LocationChange error:%s' % e)
return json.JSONEncoder().encode(res)
@http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
@http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
def AGVToProduct(self, **kw):
"""
@@ -549,7 +549,7 @@ class Manufacturing_Connect(http.Controller):
logging.info('AGVToProduct error:%s' % e)
return json.JSONEncoder().encode(res)
@http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
@http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*")
def AGVDownProduct(self, **kw):
"""
@@ -668,7 +668,7 @@ class Manufacturing_Connect(http.Controller):
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,
@http.route('/AutoDeviceApi/AgvStationState', type='json', auth='public', methods=['GET', 'POST'], csrf=False,
cors="*")
def AGVStationState(self, **kw):
"""

View File

@@ -1,10 +1,10 @@
import logging
import requests
from odoo import models, fields, api, _
from odoo.exceptions import UserError
import logging
_logger = logging.getLogger(__name__)
@@ -54,7 +54,7 @@ class AgvScheduling(models.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):
for item in domain:
if isinstance(item, list):
if item[0] == 'delivery_workpieces':
new_domain.append('&')
@@ -63,7 +63,7 @@ class AgvScheduling(models.Model):
continue
new_domain.append(item)
return super(AgvScheduling, self).web_search_read(new_domain, fields, limit=limit, offset=offset)
return super(AgvScheduling, self).web_search_read(new_domain, fields, offset, limit, order, count_limit)
@api.depends('task_completion_time', 'task_delivery_time')
def _compute_task_duration(self):
@@ -265,3 +265,18 @@ class ResMrpWorkOrder(models.Model):
'sf_agv_scheduling_mrp_workorder_ref',
string='AGV调度',
domain=[('state', '!=', '已取消')])
def get_down_product_agv_scheduling(self):
"""
获取关联的制造订单下产线的agv任务
"""
self.ensure_one()
workorder_ids = self.production_id.workorder_ids
cnc_workorder = workorder_ids.filtered(
lambda w: w.routing_type == 'CNC加工' and w.state == 'done' and w.processing_panel == self.processing_panel
)
if cnc_workorder:
agv_schedulingss = cnc_workorder.agv_scheduling_ids
return agv_schedulingss.filtered(lambda a: a.state == '已配送' and a.agv_route_type == '下产线')
else:
return None

View File

@@ -318,8 +318,10 @@ class MrpProduction(models.Model):
# cnc程序获取
def fetchCNC(self, production_names):
cnc = self.env['mrp.production'].search([('id', '=', self.id)])
quick_order = self.env['quick.easy.order'].search(
[('name', '=', cnc.product_id.default_code.rsplit('-', 1)[0])])
quick_order = False
if cnc.product_id.default_code:
quick_order = self.env['quick.easy.order'].search(
[('name', '=', cnc.product_id.default_code.rsplit('-', 1)[0])])
programme_way = False
if cnc.manual_quotation is True:
programme_way = 'manual operation'
@@ -779,7 +781,8 @@ class MrpProduction(models.Model):
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})
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):
@@ -803,6 +806,10 @@ class MrpProduction(models.Model):
backorders = backorders - productions_to_backorder
productions_not_to_backorder._post_inventory(cancel_backorder=True)
# if self.workorder_ids.filtered(lambda w: w.routing_type in ['表面工艺']):
# move_finish = self.env['stock.move'].search([('created_production_id', '=', self.id)])
# if move_finish:
# move_finish._action_assign()
productions_to_backorder._post_inventory(cancel_backorder=True)
# if completed products make other confirmed/partially_available moves available, assign them
@@ -1078,27 +1085,22 @@ class MrpProduction(models.Model):
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
stock_picking_remanufacture = self.env['stock.picking'].search([('origin', '=', productions.name)])
for pick in stock_picking_remanufacture:
if pick.name.startswith('WH/PC/') or pick.name.startswith('WH/INT/'):
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)])
@@ -1111,7 +1113,6 @@ class MrpProduction(models.Model):
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:

View File

@@ -1,7 +1,10 @@
import datetime
from datetime import timedelta, time
from collections import defaultdict
from odoo import fields, models
from odoo import fields, models, api
from odoo.addons.resource.models.resource import Intervals
from odoo.exceptions import UserError, ValidationError
import math
class ResWorkcenter(models.Model):
@@ -41,14 +44,16 @@ 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)
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)
costs_hour = fields.Float(string='Cost per hour', help='Hourly processing cost.', default=0.0, tracking=True)
equipment_status = fields.Selection(
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"), ("封存(报废)", "封存(报废)")],
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"),
("封存(报废)", "封存(报废)")],
string="设备状态", related='equipment_id.state')
# @api.depends('equipment_id')
@@ -127,6 +132,106 @@ class ResWorkcenter(models.Model):
# AGV是否可配送
is_agv_scheduling = fields.Boolean(string="AGV所属区域", tracking=True)
# 生产线优化
available_machine_number = fields.Integer(string="可用机台数量")
single_machine_capacity = fields.Float(string="单台小时产能")
production_line_hour_capacity = fields.Float(string="生产线小时产能", readonly=True,
compute='_compute_production_line_hour_capacity')
effective_working_hours_day = fields.Float(string="日有效工作时长", default=0, readonly=True,
compute='_compute_effective_working_hours_day')
default_capacity = fields.Float(
string='生产线日产能', compute='_compute_production_line_day_capacity', readonly=True)
# 计算生产线日产能
@api.depends('production_line_hour_capacity', 'effective_working_hours_day')
def _compute_production_line_day_capacity(self):
for record in self:
record.default_capacity = round(
record.production_line_hour_capacity * record.effective_working_hours_day, 2)
# 计算日有效工作时长
@api.depends('resource_calendar_id', 'resource_calendar_id.attendance_ids',
'resource_calendar_id.attendance_ids.hour_to', 'resource_calendar_id.attendance_ids.hour_from')
def _compute_effective_working_hours_day(self):
for record in self:
attendance_ids = [p for p in record.resource_calendar_id.attendance_ids if
p.dayofweek == self.get_current_day_of_week(datetime.datetime.now())]
if attendance_ids:
for attendance_id in attendance_ids:
if attendance_id.hour_from and attendance_id.hour_to:
record.effective_working_hours_day += attendance_id.hour_to - attendance_id.hour_from
else:
record.effective_working_hours_day = 0
# 获取传入时间是星期几
def get_current_day_of_week(self, datetime):
day_num = datetime.weekday()
return str(day_num)
# 计算生产线小时产能
@api.depends('single_machine_capacity', 'available_machine_number')
def _compute_production_line_hour_capacity(self):
for record in self:
record.production_line_hour_capacity = round(
record.single_machine_capacity * record.available_machine_number, 2)
# 判断计划开始时间是否在配置的工作中心的工作日历内
def deal_with_workcenter_calendar(self, start_date):
start_date = start_date + timedelta(hours=8) # 转换为北京时间
for record in self:
attendance_ids = [p for p in record.resource_calendar_id.attendance_ids if
p.dayofweek == record.get_current_day_of_week(start_date) and self.is_between_times(
p.hour_from, p.hour_to, start_date)]
return False if not attendance_ids else True
# 判断传入时间是否在配置的工作中心的工作日历内
def is_between_times(self, hour_from, hour_to, start_date):
integer_part, decimal_part = self.get_integer_and_decimal_parts(hour_from)
start_time = time(integer_part, decimal_part)
integer_part, decimal_part = self.get_integer_and_decimal_parts(hour_to)
end_time = time(integer_part, decimal_part)
return start_time <= start_date.time() <= end_time
# 获取整数部分和小数部分
def get_integer_and_decimal_parts(self, value):
integer_part = int(value)
decimal_part = value - integer_part
if decimal_part > 0:
decimal_part = round(decimal_part, 2) * 60
return int(integer_part), math.ceil(decimal_part)
# 处理排程是否超过日产能
def deal_available_default_capacity(self, date_planned):
date_planned_start = date_planned.strftime('%Y-%m-%d')
date_planned_end = date_planned + timedelta(days=1)
date_planned_end = date_planned_end.strftime('%Y-%m-%d')
plan_ids = self.env['sf.production.plan'].sudo().search([('date_planned_start', '>=', date_planned_start),
('date_planned_start', '<',
date_planned_end),
('state', 'not in', ['draft', 'cancel'])])
if plan_ids:
sum_qty = sum([p.product_qty for p in plan_ids])
if sum_qty >= self.default_capacity:
return False
return True
# 处理排程是否超过小时产能
def deal_available_single_machine_capacity(self, date_planned):
date_planned_start = date_planned.strftime('%Y-%m-%d %H:00:00')
date_planned_end = date_planned + timedelta(hours=1)
date_planned_end = date_planned_end.strftime('%Y-%m-%d %H:00:00')
plan_ids = self.env['sf.production.plan'].sudo().search([('date_planned_start', '>=', date_planned_start),
('date_planned_start', '<',
date_planned_end),
('state', 'not in', ['draft', 'cancel'])])
if plan_ids:
sum_qty = sum([p.product_qty for p in plan_ids])
if sum_qty >= self.production_line_hour_capacity:
return False
return True
class ResWorkcenterProductivity(models.Model):
_inherit = 'mrp.workcenter.productivity'

View File

@@ -144,6 +144,8 @@ class ResMrpWorkOrder(models.Model):
# 是否绑定托盘
is_trayed = fields.Boolean(string='是否绑定托盘', default=False)
tag_type = fields.Selection([("重新加工", "重新加工")], string="标签", tracking=True)
@api.depends('name', 'production_id.name')
def _compute_surface_technics_picking_ids(self):
for workorder in self:
@@ -222,7 +224,7 @@ class ResMrpWorkOrder(models.Model):
material_width = fields.Float(string='')
material_height = fields.Float(string='')
# 零件图号
part_number = fields.Char(string='零件图号')
part_number = fields.Char(related='production_id.part_number', string='零件图号')
# 工序状态
process_state = fields.Selection([
('待装夹', '待装夹'),
@@ -414,12 +416,10 @@ class ResMrpWorkOrder(models.Model):
# 获取三次元检测点数据
def get_three_check_datas(self):
factory_nick_name = 'XT'
ftp_resconfig = self.env['res.config.settings'].get_values()
ftp = FtpController(str(ftp_resconfig['ftp_host']), int(ftp_resconfig['ftp_port']),
ftp_resconfig['ftp_user'],
ftp_resconfig['ftp_password'])
# ftp.connect()
local_dir_path = '/ftp/before'
os.makedirs(local_dir_path, exist_ok=True)
@@ -428,6 +428,18 @@ class ResMrpWorkOrder(models.Model):
logging.info('local_file_path:%s' % local_file_path)
remote_path = '/home/ftp/ftp_root/ThreeTest/XT/Before/' + local_filename
logging.info('remote_path:%s' % remote_path)
is_get_detection_file = self.env['ir.config_parameter'].sudo().get_param('is_get_detection_file')
if not is_get_detection_file:
paload_data = {
"filename": local_filename
}
if not ftp_resconfig['get_check_file_path']:
raise UserError('请先配置获取检测报告地址')
url = ftp_resconfig['get_check_file_path'] + '/get/check/report'
response = requests.post(url, json=paload_data)
logging.info('response:%s' % response.json())
if response.json().get('detail'):
raise UserError(response.json().get('detail'))
if not ftp.file_exists(remote_path):
raise UserError(f"文件不存在: {remote_path}")
@@ -540,6 +552,7 @@ class ResMrpWorkOrder(models.Model):
raise UserError('PT10点未测或数据错误')
self.data_state = True
self.getcenter()
return True
@@ -593,6 +606,8 @@ class ResMrpWorkOrder(models.Model):
print("(%.2f,%.2f)" % (x, y))
self.material_center_point = ("(%.2f,%.2f,%.2f)" % (x, y, z))
self.X_deviation_angle = jdz
logging.info("坯料中心点坐标:(%.2f,%.2f)" % (x, y))
logging.info("X轴偏差度数:%.2f" % jdz)
# 将补偿值写入CNC加工工单
workorder = self.env['mrp.workorder'].browse(self.ids)
work = workorder.production_id.workorder_ids
@@ -695,6 +710,7 @@ class ResMrpWorkOrder(models.Model):
'date_planned_finished': datetime.now() + timedelta(days=1),
'duration_expected': duration_expected,
'duration': 0,
'tag_type': '重新加工' if item is False else False,
'cnc_ids': False if route.routing_type != 'CNC加工' else self.env['sf.cnc.processing']._json_cnc_processing(
k, item),
'cmm_ids': False if route.routing_type != 'CNC加工' else self.env['sf.cmm.program']._json_cmm_program(k,
@@ -1015,16 +1031,20 @@ class ResMrpWorkOrder(models.Model):
# 查询工序最小的非完工、非返工的装夹预调工单
work_id = self.search(
[('production_id', '=', workorder.production_id.id),
('routing_type', '=', '装夹预调'),
('state', 'not in', ['rework', 'done', 'cancel'])],
limit=1,
order="sequence")
if workorder == work_id:
if workorder.production_id.reservation_state == 'assigned':
workorder.state = 'ready'
elif workorder.production_id.reservation_state != 'assigned':
workorder.state = 'waiting'
continue
if work_id.routing_type == '装夹预调':
if workorder == work_id:
if workorder.production_id.reservation_state == 'assigned':
workorder.state = 'ready'
elif workorder.production_id.reservation_state != 'assigned':
workorder.state = 'waiting'
continue
elif (workorder.name == '装夹预调' and
workorder.state not in ['rework', 'done', 'cancel']):
if workorder.state != 'pending':
workorder.state = 'pending'
if workorder.production_id.tool_state in ['1', '2'] and workorder.state == 'ready':
workorder.state = 'waiting'
continue
@@ -1100,7 +1120,7 @@ class ResMrpWorkOrder(models.Model):
[('barcode', 'ilike', 'VL-SPOC')]).id),
('origin', '=', self.production_id.name)])
if move_out.state != 'done':
move_out.write({'state': 'assigned'})
move_out.write({'state': 'assigned', 'production_id': False})
self.env['stock.move.line'].create(move_out.get_move_line(self.production_id, self))
# move_out._action_assign()
@@ -1169,8 +1189,10 @@ class ResMrpWorkOrder(models.Model):
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")
if not record.material_center_point:
raise UserError("坯料中心点为空,请检查")
if record.X_deviation_angle <= 0:
raise UserError("X偏差角度小于等于0请检查本次计算的X偏差角度为%s" % record.X_deviation_angle)
record.process_state = '待加工'
# record.write({'process_state': '待加工'})
record.production_id.process_state = '待加工'
@@ -1328,6 +1350,7 @@ class ResMrpWorkOrder(models.Model):
arch = etree.fromstring(tree_view['arch'])
# 查找 tree 标签
tree_element = arch.xpath("//tree")[0]
tree_element.set('js_class', 'remove_focus_list_view')
# 查找或创建 header 标签
header_element = tree_element.find('header')
@@ -1340,7 +1363,7 @@ class ResMrpWorkOrder(models.Model):
'name': 'button_delivery',
'type': 'object',
'string': '解除装夹',
'class': 'btn-primary',
'class': 'btn-primary jikimo_button_confirm',
# 'className': 'btn-primary',
'modifiers': '{"force_show": 1}'
})
@@ -1351,18 +1374,23 @@ class ResMrpWorkOrder(models.Model):
return res
def button_delivery(self):
production_ids = []
workorder_ids = []
# 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)
# max_num = 4 # 最大配送数量
# feeder_station_start_id = False
# 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)
# if not feeder_station_start_id:
# down_product_agv_scheduling = item.get_down_product_agv_scheduling()
# if down_product_agv_scheduling:
# feeder_station_start_id = down_product_agv_scheduling.end_site_id.id
return {
'name': _('确认'),
'type': 'ir.actions.act_window',
@@ -1371,11 +1399,12 @@ class ResMrpWorkOrder(models.Model):
'target': 'new',
'context': {
# 'default_delivery_ids': [(6, 0, delivery_ids)],
'default_production_ids': [(6, 0, production_ids)],
# 'default_production_ids': [(6, 0, production_ids)],
'default_delivery_type': delivery_type,
'default_workorder_ids': [(6, 0, workorder_ids)],
# 'default_workorder_ids': [(6, 0, workorder_ids)],
'default_workcenter_id': self.env.context.get('default_workcenter_id'),
'default_confirm_button': '确认解除'
'default_confirm_button': '确认解除',
# 'default_feeder_station_start_id': feeder_station_start_id,
}}
@@ -1544,6 +1573,8 @@ class SfWorkOrderBarcodes(models.Model):
def on_barcode_scanned(self, barcode):
logging.info('Rfid:%s' % barcode)
if 'O-CMD' in barcode:
return None
workorder = self.env['mrp.workorder'].browse(self.ids)
# workorder_preset = self.env['mrp.workorder'].search(
# [('routing_type', '=', '装夹预调'), ('rfid_code', '=', barcode)])

View File

@@ -4,19 +4,39 @@ from odoo import models, fields, api
class ResConfigSettings(models.TransientModel):
_inherit = 'res.config.settings'
agv_rcs_url = fields.Char(string='avg_rcs访问地址',
default='http://172.16.10.114:8182/rcms/services/rest/hikRpcService/genAgvSchedulingTask')
wbcode = fields.Char('地码')
agv_code = fields.Char(string='agv编号')
task_type_no = fields.Char('任务单类型编号')
is_agv_task_dispatch = fields.Boolean('是否下发AGV任务', default=False)
# 是否重新获取检测文件
is_get_detection_file = fields.Boolean(string='重新获取检测文件', default=False)
@api.model
def get_values(self):
values = super(ResConfigSettings, self).get_values()
config = self.env['ir.config_parameter'].sudo()
agv_rcs_url = config.get_param('agv_rcs_url', default='')
wbcode = config.get_param('wbcode', default='')
agv_code = config.get_param('agv_code', default='')
is_agv_task_dispatch = config.get_param('is_agv_task_dispatch')
is_get_detection_file = config.get_param('is_get_detection_file')
values.update(
agv_rcs_url=agv_rcs_url,
wbcode=wbcode,
agv_code=agv_code,
is_agv_task_dispatch=is_agv_task_dispatch,
is_get_detection_file=is_get_detection_file
)
return values
def set_values(self):
super(ResConfigSettings, self).set_values()
config = self.env['ir.config_parameter'].sudo()
config.set_param("agv_rcs_url", self.agv_rcs_url or "")
config.set_param("wbcode", self.wbcode or "")
config.set_param("agv_code", self.agv_code or "")
config.set_param("is_agv_task_dispatch", self.is_agv_task_dispatch or False)
config.set_param("is_get_detection_file", self.is_get_detection_file or False)

View File

@@ -267,6 +267,11 @@ class StockRule(models.Model):
workorder_duration += workorder.duration_expected
sale_order = self.env['sale.order'].sudo().search([('name', '=', production.origin)])
# 根据销售订单号查询快速订单
quick_easy_order = self.env['quick.easy.order'].sudo().search([('sale_order_id', '=', sale_order.id)])
if quick_easy_order:
production.write({'part_number': quick_easy_order.part_drawing_number,
'part_drawing': quick_easy_order.machining_drawings})
if sale_order:
# sale_order.write({'schedule_status': 'to schedule'})
self.env['sf.production.plan'].sudo().with_company(company_id).create({
@@ -288,6 +293,7 @@ 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)],
@@ -574,6 +580,7 @@ class StockPicking(models.Model):
('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:
move_in.write({'production_id': False})
if move_out.picking_id.state != 'done':
raise UserError(
_('该入库单对应的单号为%s的出库单还未完成,不能进行验证操作!' % move_out.picking_id.name))
@@ -659,6 +666,11 @@ class ReStockMove(models.Model):
return move_values
def _get_new_picking_values_Res(self, item, sorted_workorders, rescode):
picking_type_id = self.mapped('picking_type_id').id
if rescode == 'WH/OCOUT/':
picking_type_id = self.env.ref('sf_manufacturing.outcontract_picking_out').id
elif rescode == 'WH/OCIN/':
picking_type_id = self.env.ref('sf_manufacturing.outcontract_picking_in').id
return {
'name': self.env['stock.picking']._get_name_Res(rescode),
'origin': item.name,
@@ -667,7 +679,7 @@ class ReStockMove(models.Model):
'user_id': False,
'move_type': self.mapped('group_id').move_type or 'direct',
'partner_id': sorted_workorders.supplier_id.id,
'picking_type_id': self.mapped('picking_type_id').id,
'picking_type_id': picking_type_id,
'location_id': self.mapped('location_id').id,
'location_dest_id': self.mapped('location_dest_id').id,
'state': 'confirmed',

View File

@@ -10,7 +10,7 @@ odoo.define('sf_manufacturing.action_dispatch_confirm', function (require) {
title: "确认",
$content: $('<div>').append("请确认是否仅配送" + params.workorder_count + "个工件?"),
buttons: [
{ text: "确认", classes: 'btn-primary', close: true, click: () => dispatchConfirmed(parent, params) },
{ text: "确认", classes: 'btn-primary jikimo_button_confirm', close: true, click: () => dispatchConfirmed(parent, params) },
{ text: "取消", close: true },
],
});

View File

@@ -6,9 +6,9 @@
<field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
<field name="arch" type="xml">
<!-- <button name="action_cancel" position="before"> -->
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
<!-- </button> -->
<!-- <button name="action_cancel" position="before"> -->
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
<!-- </button> -->
<div name="button_box" position="inside">
<button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench"
attrs="{'invisible': [('maintenance_count', '=', 0)]}"
@@ -25,38 +25,81 @@
</record>
<record id="custom_model_form_view_inherit" model="ir.ui.view">
<field name="name">custom.model.form.view.inherit</field>
<field name="model">mrp.workcenter</field>
<field name="inherit_id" ref="mrp.mrp_workcenter_view"/>
<field name="arch" type="xml">
<xpath expr='//form//sheet' position="after">
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="message_ids"/>
</div>
</xpath>
</field>
</record>
<field name="name">custom.model.form.view.inherit</field>
<field name="model">mrp.workcenter</field>
<field name="inherit_id" ref="mrp.mrp_workcenter_view"/>
<field name="arch" type="xml">
<xpath expr='//form//sheet' position="after">
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="message_ids"/>
</div>
</xpath>
<xpath expr="//field[@name='default_capacity'][last()]" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//field[@name='default_capacity'][last()]" position="after">
<record id="mrp_workcenter_view_kanban_inherit_workorder" model="ir.ui.view">
<field name="name">mrp.workcenter.view.kanban.inherit.mrp.workorder</field>
<field name="model">mrp.workcenter</field>
<field name="inherit_id" ref="mrp.mrp_workcenter_kanban"/>
<field name="arch" type="xml">
<!-- Desktop view -->
<xpath expr='(//field[@name="name"])[1]' position="after">
<field name="equipment_status" />
<field name="equipment_image" />
</xpath>
<xpath expr='(//field[@name="name"])[2]' position="after">
<field name="equipment_status" />
<field name="equipment_image" widget="image" />
</xpath>
<xpath expr='(//a[@name="unblock"])' position="after">
<div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>
</xpath>
</field>
</record>
<label for="default_capacity"/>
<div class="o_row">
<field name="default_capacity" string="产线日产能"/>
</div>
</xpath>
<xpath expr="//field[@name='default_capacity'][last()]" position="before">
<label for="available_machine_number"/>
<div class="o_row">
<field name="available_machine_number"/>
</div>
<label for="single_machine_capacity"/>
<div class="o_row">
<field name="single_machine_capacity"/>
件/小时
</div>
<label for="production_line_hour_capacity"/>
<div class="o_row">
<field name="production_line_hour_capacity"/>
件/小时
</div>
<label for="effective_working_hours_day"/>
<div class="o_row">
<field name="effective_working_hours_day"/>
小时
</div>
</xpath>
<!-- <xpath expr='//group[@name="capacity"]//field[@name="default_capacity"])' position="after">-->
<!-- <group>-->
<!-- <field name="available_machine_number"/>-->
<!-- <field name="single_machine_capacity"/>-->
<!-- <field name="production_line_hour_capacity"/>-->
<!-- <field name="effective_working_hours_day"/>-->
<!-- </group>-->
<!-- </xpath>-->
</field>
</record>
<record id="mrp_workcenter_view_kanban_inherit_workorder" model="ir.ui.view">
<field name="name">mrp.workcenter.view.kanban.inherit.mrp.workorder</field>
<field name="model">mrp.workcenter</field>
<field name="inherit_id" ref="mrp.mrp_workcenter_kanban"/>
<field name="arch" type="xml">
<!-- Desktop view -->
<xpath expr='(//field[@name="name"])[1]' position="after">
<field name="equipment_status"/>
<field name="equipment_image"/>
</xpath>
<xpath expr='(//field[@name="name"])[2]' position="after">
<field name="equipment_status"/>
<field name="equipment_image" widget="image"/>
</xpath>
<xpath expr='(//a[@name="unblock"])' position="after">
<div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>
</xpath>
</field>
</record>
<record id="mrp_workcenter_view_kanban_inherit_workorder" model="ir.ui.view">
<field name="name">mrp.workcenter.view.kanban.inherit.mrp.workorder</field>
@@ -73,11 +116,11 @@
</xpath>
<xpath expr='(//a[@name="unblock"])' position="after">
<!-- <div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>-->
<!-- <div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>-->
</xpath>
</field>
</record>
<!--&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;-->
<!--&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;-->
<record id="mrp_workcenter_kanban_inherit1" model="ir.ui.view">
<field name="name">mrp.workcenter.kanban.inherit</field>
<field name="model">mrp.workcenter</field>
@@ -99,7 +142,7 @@
</field>
</record>
<!-- 继承原有的看板视图 -->
<!-- 继承原有的看板视图 -->
<record id="mrp_workcenter_kanban_inherit1" model="ir.ui.view">
<field name="name">mrp.workcenter.kanban.inherit</field>
<field name="model">mrp.workcenter</field>
@@ -391,9 +434,9 @@
<field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
<field name="arch" type="xml">
<!-- <button name="action_cancel" position="before"> -->
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
<!-- </button> -->
<!-- <button name="action_cancel" position="before"> -->
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
<!-- </button> -->
<div name="button_box" position="inside">
<button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench"
attrs="{'invisible': [('maintenance_count', '=', 0)]}"

View File

@@ -32,22 +32,24 @@
</field>
<xpath expr="//field[@name='qty_remaining']" position="after">
<field name="manual_quotation" optional="show"/>
<field name='tag_type' widget="badge"
decoration-danger="tag_type == '重新加工'"/>
</xpath>
<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"/>
<field name="reserved_duration" string="计划预留时间" optional="hide"/>
</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加工")]}-->
<!-- </attribute>-->
<attribute name="attrs">{'invisible': ['|', '|', '|','|','|', ('production_state','in', ('draft',
'done',
'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel')),
@@ -165,8 +167,8 @@
<!-- 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)]}"/>-->
<button name="button_start" type="object" string="开始" class="btn-success" confirm="是否确认开始"
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"
@@ -191,8 +193,8 @@
<!-- 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)]}"/>
@@ -221,9 +223,12 @@
<xpath expr="//label[1]" position="before">
<field name='routing_type' readonly="1"/>
<field name='process_state' attrs='{"invisible": [("routing_type","!=","装夹预调")]}'/>
<field name='tag_type' readonly="1" attrs='{"invisible": [("tag_type","=",False)]}'
decoration-danger="tag_type == '重新加工'"/>
<field name="rfid_code" force_save="1" readonly="1" cache="True"
attrs="{'invisible': [('rfid_code_old', '!=', False)]}"/>
<field name="rfid_code_old" readonly="1" attrs="{'invisible': [('rfid_code_old', '=', False)]}"/>
</xpath>
<xpath expr="//label[1]" position="attributes">
<attribute name="string">计划加工时间</attribute>
@@ -479,10 +484,10 @@
<div class="col-12 col-lg-6 o_setting_box">
<field name="data_state" invisible="1"/>
<button type="object" class="oe_highlight" name="get_three_check_datas" string="获取数据"
attrs='{"invisible": ["|", "|", "|", ("material_center_point","!=",False),("state","!=","progress"),("user_permissions","=",False), ("data_state", "=", True)]}'/>
<button type="object" class="oe_highlight" name="getcenter" string="计算定位"
attrs='{"invisible": ["|","|", "|",("material_center_point","!=",False),("state","!=","progress"),("user_permissions","=",False), ("data_state", "=", False)]}'/>
<!-- <button type="object" class="oe_highlight" name="get_three_check_datas" string="获取数据" -->
<!-- attrs='{"invisible": ["|", "|", "|", ("material_center_point","!=",False),("state","!=","progress"),("user_permissions","=",False), ("data_state", "=", True)]}'/> -->
<!-- <button type="object" class="oe_highlight" name="getcenter" string="计算定位" -->
<!-- attrs='{"invisible": ["|","|", "|",("material_center_point","!=",False),("state","!=","progress"),("user_permissions","=",False), ("data_state", "=", False)]}'/> -->
</div>
<group>
@@ -513,6 +518,11 @@
</xpath>
<xpath expr="//form//header" position="inside">
<button type="object" class="oe_highlight jikimo_button_confirm" name="get_three_check_datas"
string="获取数据" attrs='{"invisible": [("state","!=","progress")]}'/>
</xpath>
<xpath expr="//page[1]" position="before">
<field name="results" invisible="1"/>
@@ -659,9 +669,10 @@
<field name="name">工件配送</field>
<field name="model">sf.workpiece.delivery</field>
<field name="arch" type="xml">
<tree string="工件配送" class="center" create="0" delete="0">
<tree string="工件配送" class="center" create="0" delete="0" js_class="remove_focus_list_view">
<header>
<button name="button_delivery" type="object" string="工件配送" class="btn-primary" attrs="{'force_show':1}"/>
<button name="button_delivery" type="object" string="工件配送"
class="btn-primary jikimo_button_confirm" attrs="{'force_show':1}"/>
</header>
<field name="status" widget="badge"
decoration-success="status == '已配送'"
@@ -673,15 +684,15 @@
<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>
@@ -848,10 +859,10 @@
<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"/>
<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

@@ -6,6 +6,32 @@
<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[hasclass('app_settings_block')]/div" position="before">
<div>
<h2>AGV参数配置</h2>
<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">
<div class="text-muted">
<label for="agv_rcs_url" string="访问地址"/>
<field name="agv_rcs_url"/>
</div>
<div class="text-muted">
<label for="agv_code" string="车辆编号"/>
<field name="agv_code"/>
</div>
<div class="text-muted">
<label for="wbcode"/>
<field name="wbcode"/>
</div>
</div>
</div>
</div>
</div>
</xpath>
<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">

View File

@@ -25,6 +25,7 @@ class ReworkWizard(models.TransientModel):
processing_panel_id = fields.Many2many('sf.processing.panel', string="加工面")
is_reprogramming = fields.Boolean(string='申请重新编程', default=False)
is_reprogramming_readonly = fields.Boolean(string='申请重新编程(只读)', default=False)
is_clamp_measure = fields.Boolean(string='保留装夹测量数据', default=True)
reprogramming_num = fields.Integer('重新编程次数', default=0)
programming_state = fields.Selection(
[('待编程', '待编程'), ('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'),
@@ -35,6 +36,7 @@ class ReworkWizard(models.TransientModel):
def confirm(self):
if self.routing_type in ['装夹预调', 'CNC加工']:
self.is_clamp_measure = False
self.workorder_id.is_rework = True
self.production_id.write({'detection_result_ids': [(0, 0, {
'rework_reason': self.rework_reason,
@@ -58,19 +60,15 @@ class ReworkWizard(models.TransientModel):
if processing_panels_missing:
processing_panels_str = ','.join(processing_panels_missing)
raise UserError('您还有待处理的检测结果中为%s的加工面未选择' % processing_panels_str)
# processing_panels = set()
# for handle_item in handle_result:
# for dr_panel in self.processing_panel_id:
# if dr_panel.name == handle_item.processing_panel:
# processing_panels.add(dr_panel.name)
# if len(processing_panels) != len(handle_result):
# processing_panels_str = ','.join(processing_panels)
# return UserError(f'您还有待处理的检测结果中为{processing_panels_str}的加工面未选择')
for panel in self.processing_panel_id:
panel_workorder = self.production_id.workorder_ids.filtered(
lambda ap: ap.processing_panel == panel.name and ap.state != 'rework')
if panel_workorder:
panel_workorder.write({'state': 'rework'})
rework_clamp_workorder = max(panel_workorder.filtered(
lambda
rp: rp.processing_panel == panel.name and rp.routing_type == '装夹预调' and rp.state in [
'done', 'rework']))
# panel_workorder.filtered(
# lambda wo: wo.routing_type == '装夹预调').workpiece_delivery_ids.filtered(
# lambda wd: wd.status == '待下发').write({'status': '已取消'})
@@ -93,6 +91,43 @@ class ReworkWizard(models.TransientModel):
self.production_id.detection_result_ids.filtered(
lambda ap1: ap1.processing_panel == panel.name and ap1.handle_result == '待处理').write(
{'handle_result': '已处理'})
new_pre_workorder = self.production_id.workorder_ids.filtered(lambda
p: p.routing_type == '装夹预调' and p.processing_panel == panel.name and p.state not in (
'rework', 'done'))
if new_pre_workorder and rework_clamp_workorder and self.is_clamp_measure is True:
new_pre_workorder.write(
{'X1_axis': rework_clamp_workorder.X1_axis, 'Y1_axis': rework_clamp_workorder.Y1_axis
, 'Z1_axis': rework_clamp_workorder.Z1_axis,
'X2_axis': rework_clamp_workorder.X2_axis
, 'Y2_axis': rework_clamp_workorder.Y2_axis,
'Z2_axis': rework_clamp_workorder.Z2_axis
, 'X3_axis': rework_clamp_workorder.X3_axis,
'Y3_axis': rework_clamp_workorder.Y3_axis
, 'Z3_axis': rework_clamp_workorder.Z3_axis,
'X4_axis': rework_clamp_workorder.X4_axis
, 'Y4_axis': rework_clamp_workorder.Y4_axis,
'Z4_axis': rework_clamp_workorder.Z4_axis
, 'X5_axis': rework_clamp_workorder.X5_axis,
'Y5_axis': rework_clamp_workorder.Y5_axis
, 'Z5_axis': rework_clamp_workorder.Z5_axis,
'X6_axis': rework_clamp_workorder.X6_axis
, 'Y6_axis': rework_clamp_workorder.Y6_axis,
'Z6_axis': rework_clamp_workorder.Z6_axis
, 'X7_axis': rework_clamp_workorder.X7_axis,
'Y7_axis': rework_clamp_workorder.Y7_axis
, 'Z7_axis': rework_clamp_workorder.Z7_axis,
'X8_axis': rework_clamp_workorder.X8_axis
, 'Y8_axis': rework_clamp_workorder.Y8_axis,
'Z8_axis': rework_clamp_workorder.Z8_axis
, 'X9_axis': rework_clamp_workorder.X9_axis,
'Y9_axis': rework_clamp_workorder.Y9_axis
, 'Z9_axis': rework_clamp_workorder.Z9_axis,
'X10_axis': rework_clamp_workorder.X10_axis
, 'Y10_axis': rework_clamp_workorder.Y10_axis,
'Z10_axis': rework_clamp_workorder.Z10_axis
, 'X_deviation_angle': rework_clamp_workorder.X_deviation_angle,
'material_center_point': rework_clamp_workorder.material_center_point
})
if self.is_reprogramming is False:
if self.programming_state in ['已编程', '已下发']:
if self.reprogramming_num >= 1 and self.programming_state == '已编程':
@@ -149,9 +184,7 @@ class ReworkWizard(models.TransientModel):
'cmm_ids': new_cnc_workorder.cmm_ids.sudo()._json_cmm_program(panel.name,
ret),
'cnc_worksheet': cnc_rework.cnc_worksheet})
new_pre_workorder = self.production_id.workorder_ids.filtered(lambda
p: p.routing_type == '装夹预调' and p.processing_panel == panel.name and p.state not in (
'rework', 'done'))
if new_pre_workorder:
pre_rework = max(self.production_id.workorder_ids.filtered(
lambda pr: pr.processing_panel == panel.name and pr.state in (

View File

@@ -14,17 +14,25 @@
<group>
<field name="processing_panel_id" options="{'no_create': True}"
attrs='{"invisible": [("routing_type","=","装夹预调")]}' widget="many2many_tags"/>
</group>
<div attrs='{"invisible": [("routing_type","=","装夹预调")]}'>
<span style='font-weight:bold;'>保留装夹测量数据
<field name="is_clamp_measure" force_save="1"/>
</span>
</div>
<div attrs='{"invisible": [("reprogramming_num","=",0)]}'>
注意: 该制造订单产品已申请重新编程次数为<field
name="reprogramming_num" string=""
readonly="1"
style='color:red;'/>,且当前编程状态为
<field name="programming_state" string=""
decoration-info="programming_state == '待编程'"
decoration-success="programming_state == '已下发'"
decoration-warning="programming_state =='编程中'"
decoration-danger="programming_state =='编程'" readonly="1"/>
<span style='font-weight:bold;'>
注意: 该制造订单产品已申请重新编程次数为<field
name="reprogramming_num" string=""
readonly="1"
style='color:red;'/>,且当前编程状态为
<field name="programming_state" string=""
decoration-info="programming_state == '待编程'"
decoration-success="programming_state == '已下发'"
decoration-warning="programming_state =='编程'"
decoration-danger="programming_state =='已编程'" readonly="1"/>
</span>
</div>
<div attrs='{"invisible": ["|",("routing_type","in",["装夹预调","CNC加工"]),("programming_state","not in",["已下发"])],"readonly": [("tool_state", "=", "2")]}'>
<span style='font-weight:bold;'>申请重新编程

View File

@@ -4,7 +4,7 @@
<field name="name">sf.workpiece.delivery.wizard.form.view</field>
<field name="model">sf.workpiece.delivery.wizard</field>
<field name="arch" type="xml">
<form js_class="remove_focus_view">
<form js_class="remove_focus_form_view">
<sheet>
<field name="delivery_ids" invisible="True"/>
<field name="workorder_ids" invisible="True"/>
@@ -14,12 +14,12 @@
<group col="1">
<field name="production_ids" readonly="1" widget="many2many_tags" string="制造订单号"/>
<field name="delivery_type" readonly="1"/>
<field name="feeder_station_start_id" options="{'no_create': True}" required="1"/>
<field name="feeder_station_start_id" string="当前接驳站" options="{'no_create': True}" required="1"/>
<field name="workcenter_id" options="{'no_create': True}"/>
</group>
<footer>
<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="确认配送" name="confirm" type="object" class="oe_highlight o_wizard_confirm_button jikimo_button_confirm" attrs="{'invisible': [('confirm_button', '!=', '确认配送')]}"/>
<button string="确认解除" name="confirm" type="object" class="oe_highlight o_wizard_confirm_button jikimo_button_confirm" attrs="{'invisible': [('confirm_button', '!=', '确认解除')]}"/>
<button string="取消" class="btn btn-secondary" special="cancel"/>
</footer>
</sheet>

View File

@@ -1,9 +1,8 @@
# -*- 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, date
from odoo.exceptions import UserError
from odoo import models, api, fields
@@ -79,29 +78,18 @@ class WorkpieceDeliveryWizard(models.TransientModel):
'tag': 'display_notification',
'target': 'new',
'params': {
'message': '任务下发成功AGV任务调度编号为【%s' % scheduling['name'],
'message': f'任务下发成功AGV任务调度编号为【{scheduling["name"]}',
'type': 'success',
'sticky': False,
'next': {'type': 'ir.actions.act_window_close'},
}
}
def confirm(self):
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:
if not self.workorder_ids:
raise UserError('制造订单不能为空')
scheduling = self.env['sf.agv.scheduling'].add_scheduling(
agv_start_site_name=self.feeder_station_start_id.name,
agv_route_type=self.delivery_type,
@@ -129,10 +117,21 @@ class WorkpieceDeliveryWizard(models.TransientModel):
item.button_start()
item.button_finish()
return scheduling.read()[0]
# return scheduling.read()[0]
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'target': 'new',
'params': {
'message': f'任务下发成功AGV任务调度编号为【{scheduling.name}',
'type': 'success',
'sticky': False,
'next': {'type': 'ir.actions.act_window_close'},
}
}
except Exception as e:
logging.info('%s任务下发失败:%s' % (self.delivery_type, e))
raise UserError('%s任务下发失败:%s' % (self.delivery_type, e))
logging.info('%s任务下发失败:%s', self.delivery_type, e)
raise UserError(f'{self.delivery_type}任务下发失败:{e}') from e
# def recognize_production(self):
# # production_ids = []
@@ -181,6 +180,14 @@ class WorkpieceDeliveryWizard(models.TransientModel):
def on_barcode_scanned(self, barcode):
delivery_type = self.env.context.get('default_delivery_type')
# 判断barcode是否是数字
if not barcode.isdigit():
# 判断是否是AGV接驳站名称
agv_site = self.env['sf.agv.site'].search([('name', '=', barcode)])
if agv_site:
self.feeder_station_start_id = agv_site.id # 修正:移除 .id
return
if delivery_type == '上产线':
workorder = self.env['mrp.workorder'].search(
[('production_line_state', '=', '待上产线'), ('rfid_code', '=', barcode),
@@ -198,11 +205,32 @@ class WorkpieceDeliveryWizard(models.TransientModel):
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)
raise UserError(f'该rfid对应的制造订单号为{workorder.production_id.name}的目的生产线不一致')
# 调用打印成品条码方法
workorder.print_method()
# 将对象添加到对应的同模型且是多对多类型里
self.production_ids |= workorder.production_id
self.workorder_ids |= workorder
down_product_agv_scheduling = workorder.get_down_product_agv_scheduling()
if down_product_agv_scheduling:
if not self.feeder_station_start_id:
self.feeder_station_start_id = down_product_agv_scheduling.end_site_id.id
else:
if self.feeder_station_start_id.id != down_product_agv_scheduling.end_site_id.id:
raise UserError(f'该rfid不在{self.feeder_station_start_id.name}接驳站内')
else:
raise UserError('该rfid码对应的工单不存在')
return
@api.onchange('feeder_station_start_id')
def on_start_id_change(self):
if self.delivery_type == '运送空料架' and len(self.workorder_ids) > 0:
down_product_agv_scheduling = self.workorder_ids[0].get_down_product_agv_scheduling()
if down_product_agv_scheduling and self.feeder_station_start_id \
and down_product_agv_scheduling.end_site_id.id != self.feeder_station_start_id.id:
raise UserError('当前接驳站不匹配!')

1
sf_message/__init__.py Normal file
View File

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

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': '机企猫智能工厂 消息提醒',
'version': '1.0',
'summary': '智能工厂消息提醒模块',
'sequence': 1,
'description': """
""",
'category': 'sf',
'website': 'https://www.sf.jikimo.com',
'depends': ['base', 'sf_plan', 'sf_sale'],
'data': [
'security/ir.model.access.csv',
'views/sf_message_template_view.xml',
],
'test': [
],
'license': 'LGPL-3',
'installable': True,
'auto_install': False,
'application': False,
}

View File

@@ -0,0 +1,8 @@
from . import sf_message_template
from . import sf_message_sale
from . import sf_message_plan
from . import sf_message_stock_picking
from . import sf_message_cam_program
from . import sf_message_functional_tool_assembly
from . import sf_message_purchase
from . import sf_message_workorder

View File

@@ -0,0 +1,6 @@
from odoo import models, fields, api, _
class SFMessageCamProgram(models.Model):
_name = 'sf.cam.work.order.program.knife.plan'
_inherit = ['sf.cam.work.order.program.knife.plan', 'sf.message.template']

View File

@@ -0,0 +1,6 @@
from odoo import models, fields, api, _
class SFMessagefunctionalToolAssembly(models.Model):
_name = 'sf.functional.tool.assembly'
_inherit = ['sf.functional.tool.assembly', 'sf.message.template']

View File

@@ -0,0 +1,6 @@
from odoo import models, fields, api, _
class SFMessagePlan(models.Model):
_name = 'sf.production.plan'
_inherit = ['sf.production.plan', 'sf.message.template']

View File

@@ -0,0 +1,6 @@
from odoo import models, fields, api, _
class SFMessagePurchase(models.Model):
_name = 'purchase.order'
_inherit = ['purchase.order', 'sf.message.template']

View File

@@ -0,0 +1,11 @@
from odoo import models, fields, api, _
class SFMessageSale(models.Model):
_name = 'sale.order'
_inherit = ['sale.order', 'sf.message.template']
# def create(self):
# res = super(SFMessageSale, self).create()
# if res is True:

View File

@@ -0,0 +1,6 @@
from odoo import models, fields, api, _
class SFMessageStockPicking(models.Model):
_name = 'stock.picking'
_inherit = ['stock.picking', 'sf.message.template']

View File

@@ -0,0 +1,34 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
from abc import ABC, abstractmethod
class SfMessageTemplate(models.Model):
_name = "sf.message.template"
_description = u'消息模板'
name = fields.Char(string=u"名称", required=True)
description = fields.Char(string=u"描述")
content = fields.Html(string=u"内容", render_engine='qweb', translate=True, prefetch=True, sanitize=False)
msgtype = fields.Selection(
[('text', u'文字'), ('markdown', u'Markdown')], u'消息类型',
required=True, default='markdown')
notification_department_id = fields.Many2one('hr.department', u'通知部门', required=True)
notification_employee_ids = fields.Many2many('hr.employee', string=u'员工',
domain="[('department_id', '=',notification_department_id)]",
required=True)
is_send_time = fields.Boolean(string=u"定时发送", default=False)
send_time_1 = fields.Integer('发送时间点1')
send_time_2 = fields.Integer('发送时间点2')
active = fields.Boolean(string=u"是否有效", default=True)
@api.onchange('notification_department_id')
def _clear_employee_ids(self):
if self.notification_department_id:
self.notification_employee_ids = False
@abstractmethod
def dispatch(self, args):
"""
强迫继承该类必走该抽象方法'
"""

View File

@@ -0,0 +1,6 @@
from odoo import models, fields, api, _
class SFMessageWork(models.Model):
_name = 'mrp.workorder'
_inherit = ['mrp.workorder', 'sf.message.template']

View File

@@ -0,0 +1,5 @@
<odoo>
<data>
</data>
</odoo>

View File

@@ -0,0 +1,15 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sf_message_template_group_sale_salemanager,sf_message_template,model_sf_message_template,sf_base.group_sale_salemanager,1,1,1,0
access_sf_message_template_group_purchase,sf_message_template,model_sf_message_template,sf_base.group_purchase,1,1,1,0
access_sf_message_template_group_sf_stock_user,sf_message_template,model_sf_message_template,sf_base.group_sf_stock_user,1,1,1,0
access_sf_message_template_group_sf_order_user,sf_message_template,model_sf_message_template,sf_base.group_sf_order_user,1,1,1,0
access_sf_message_template_group_sf_tool_user,sf_message_template,model_sf_message_template,sf_base.group_sf_tool_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sf_message_template_group_sale_salemanager sf_message_template model_sf_message_template sf_base.group_sale_salemanager 1 1 1 0
3 access_sf_message_template_group_purchase sf_message_template model_sf_message_template sf_base.group_purchase 1 1 1 0
4 access_sf_message_template_group_sf_stock_user sf_message_template model_sf_message_template sf_base.group_sf_stock_user 1 1 1 0
5 access_sf_message_template_group_sf_order_user sf_message_template model_sf_message_template sf_base.group_sf_order_user 1 1 1 0
6 access_sf_message_template_group_sf_tool_user sf_message_template model_sf_message_template sf_base.group_sf_tool_user 1 1 1 0

View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- © <2016> <top hy>
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). -->
<odoo>
<data>
<record id="sf_message_template_view_form" model="ir.ui.view">
<field name="name">sf.message.template.view.form</field>
<field name="model">sf.message.template</field>
<field name="arch" type="xml">
<form string="消息模板">
<sheet>
<div class="oe_title">
<label for="name"/>
<h1>
<field name="name" class="w-100" required="1"/>
</h1>
</div>
<group>
<!-- <field name="type"/>-->
<field name="content" widget="html" class="oe-bordered-editor"
options="{'style-inline': true, 'codeview': true, 'dynamic_placeholder': true}"/>
<field name="description"/>
<field name="msgtype"/>
<field name="is_send_time"/>
<field name="send_time_1" attrs="{'invisible': [('is_send_time', '=', False)]}"/>
<field name="send_time_2" attrs="{'invisible': [('is_send_time', '=', False)]}"/>
<field name="notification_department_id"/>
<field name="notification_employee_ids" widget="many2many_tags"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="sf_message_template_view_tree" model="ir.ui.view">
<field name="name">sf.message.template.view.tree</field>
<field name="model">sf.message.template</field>
<field name="arch" type="xml">
<tree string="消息模板">
<field name="name"/>
<!-- <field name="type"/>-->
<field name="content"/>
<field name="msgtype"/>
<field name="is_send_time"/>
<field name="send_time_1" attrs="{'invisible': [('is_send_time', '=', False)]}"/>
<field name="send_time_2" attrs="{'invisible': [('is_send_time', '=', False)]}"/>
<field name="notification_department_id"/>
<field name="notification_employee_ids" widget="many2many_tags"/>
<field name="description"/>
</tree>
</field>
</record>
<record id="sf_message_template_search_view" model="ir.ui.view">
<field name="name">sf.message.template.search.view</field>
<field name="model">sf.message.template</field>
<field name="arch" type="xml">
<search>
<field name="name" string="模糊搜索"
filter_domain="['|','|',('name','like',self),('description','like',self)]"/>
<field name="name"/>
<filter name="filter_active" string="已归档" domain="[('active','=',False)]"/>
</search>
</field>
</record>
<!--定义单证类型视图动作-->
<record id="sf_message_template_action" model="ir.actions.act_window">
<field name="name">消息模板</field>
<field name="res_model">sf.message.template</field>
<field name="view_mode">tree,form</field>
<field name="view_id" ref="sf_message_template_view_tree"/>
</record>
<menuitem id="msg_set_menu" name="消息设置" parent="base.menu_administration" sequence="1"/>
<menuitem id="sf_message_template_send_menu" name="消息模板" parent="msg_set_menu"
action="sf_message_template_action" sequence="1"/>
</data>
</odoo>

View File

@@ -10,11 +10,12 @@
""",
'category': 'sf',
'website': 'https://www.sf.cs.jikimo.com',
'depends': ['sf_base', 'base_setup'],
'depends': ['sf_base', 'base_setup','sf_bf_connect'],
'data': [
'data/ir_cron_data.xml',
'security/ir.model.access.csv',
'views/res_config_settings_views.xml'
'views/res_config_settings_views.xml',
'views/order_price.xml',
],
'demo': [
],

View File

@@ -1,3 +1,4 @@
from . import ftp_operate
from . import res_config_setting
from . import sync_common
from . import order_price

View File

@@ -0,0 +1,29 @@
from odoo import fields, models, api
class OrderPrice(models.Model):
_name = 'order.price'
_description = '订单价格对比'
sale_order_id = fields.Many2one('sale.order', '销售订单')
bfm_order_name = fields.Char(related="sale_order_id.default_code", string='bfm订单号')
sale_order_name = fields.Char(related="sale_order_id.name", string='销售订单号')
currency_id = fields.Many2one(
related='sale_order_id.currency_id', string='货币', store=True)
sale_order_amount_total = fields.Monetary(related="sale_order_id.amount_total", tracking=4, string='销售订单金额')
bfm_amount_total = fields.Float(string='价格合计', compute='_compute_bfm_amount_total', store=True)
def is_float(self,value):
try:
float(value)
return True
except ValueError:
return False
@api.depends('sale_order_id.remark')
def _compute_bfm_amount_total(self):
for record in self:
amount_total = 0
for line in record.sale_order_id.order_line:
# 判断remark是否存在并且是否是数字
if line.remark and self.is_float(line.remark):
amount_total += float(line.remark)
record.bfm_amount_total = amount_total

View File

@@ -1,8 +1,11 @@
# -*- coding: utf-8 -*-
# Part of SmartGo. See LICENSE file for full copyright and licensing details.
import logging
import requests
from odoo import api, fields, models
from odoo.exceptions import ValidationError
from odoo.exceptions import ValidationError, UserError
_logger = logging.getLogger(__name__)
@@ -13,13 +16,10 @@ class ResConfigSettings(models.TransientModel):
token = fields.Char(string='TOKEN', default='b811ac06-3f00-11ed-9aed-0242ac110003')
sf_secret_key = fields.Char(string='密钥', default='wBmxej38OkErKhD6')
sf_url = fields.Char(string='访问地址', default='https://sf.cs.jikimo.com')
agv_rcs_url = fields.Char(string='avg_rcs访问地址',
default='http://172.16.10.114:8182/rcms/services/rest/hikRpcService/genAgvSchedulingTask')
center_control_url = fields.Char(string='中控访问地址',
default='http://172.16.21.50:8001')
center_control_Authorization = fields.Char(string='中控访问认证')
wbcode = fields.Char('地码')
agv_code = fields.Char(string='agv编号')
task_type_no = fields.Char('任务单类型编号')
model_parser_url = fields.Char('特征识别路径')
ftp_host = fields.Char(string='FTP的ip')
@@ -86,7 +86,7 @@ class ResConfigSettings(models.TransientModel):
_logger.info("同步刀具物料每齿走刀量完成")
except Exception as e:
_logger.info("捕获错误信息:%s" % e)
_logger.info("sf_all_sync error: %s" % e)
raise ValidationError("数据错误导致同步失败,请联系管理员")
@api.model
@@ -100,9 +100,7 @@ class ResConfigSettings(models.TransientModel):
token = config.get_param('token', default='')
sf_secret_key = config.get_param('sf_secret_key', default='')
sf_url = config.get_param('sf_url', default='')
agv_rcs_url = config.get_param('agv_rcs_url', default='')
wbcode = config.get_param('wbcode', default='')
agv_code = config.get_param('agv_code', default='')
center_control_url = config.get_param('center_control_url', default='')
center_control_Authorization = config.get_param('center_control_Authorization', default='')
ftp_host = config.get_param('ftp_host', default='')
@@ -115,9 +113,7 @@ class ResConfigSettings(models.TransientModel):
token=token,
sf_secret_key=sf_secret_key,
sf_url=sf_url,
agv_rcs_url=agv_rcs_url,
wbcode=wbcode,
agv_code=agv_code,
center_control_url=center_control_url,
center_control_Authorization=center_control_Authorization,
ftp_host=ftp_host,
@@ -134,9 +130,7 @@ class ResConfigSettings(models.TransientModel):
ir_config.set_param("token", self.token or "")
ir_config.set_param("sf_secret_key", self.sf_secret_key or "")
ir_config.set_param("sf_url", self.sf_url or "")
ir_config.set_param("agv_rcs_url", self.agv_rcs_url or "")
ir_config.set_param("wbcode", self.wbcode or "")
ir_config.set_param("agv_code", self.agv_code or "")
ir_config.set_param("center_control_url", self.center_control_url or "")
ir_config.set_param("center_control_Authorization", self.center_control_Authorization or "")
ir_config.set_param("ftp_host", self.ftp_host or "")
@@ -144,3 +138,71 @@ class ResConfigSettings(models.TransientModel):
ir_config.set_param("ftp_user", self.ftp_user or "")
ir_config.set_param("ftp_password", self.ftp_password or "")
ir_config.set_param("enable_tool_presetter", self.enable_tool_presetter or False)
def sync_sale_price(self):
self.get_page_all_records(self.sale_order_price_process)
def sale_order_price_process(self, datas):
if not datas:
return
try:
convert_sale_data = map(lambda data:
{'name': data.name, 'order_lines': {
str(i): str(value.price_unit) for i, value in enumerate(data.order_line)
}
},
datas)
config = self.env['res.config.settings'].get_values()
url = config['bfm_url_new'] + '/api/sync/order/price'
json_data = {
'params': {
'data': list(convert_sale_data),
},
}
response = requests.post(url, json=json_data, data=None)
response = response.json()
if not response.get('error'):
result = response.get('result')
for need_change_sale_data in result:
res_order_lines_map = need_change_sale_data.get('order_lines')
if not res_order_lines_map:
continue
need_change_sale_order = self.env['sale.order'].sudo().search([('name', '=', need_change_sale_data.get('name'))])
for index,need_change_sale_order_line in enumerate(need_change_sale_order.order_line):
if not res_order_lines_map.get(str(index)):
continue
order_line = self.env['sale.order.line'].browse(need_change_sale_order_line.id)
new_price = res_order_lines_map.get(str(index))
if order_line:
# 修改单价
order_line.write({'remark': new_price*order_line.product_uom_qty})
order_price = self.env['order.price'].sudo().search([('sale_order_id', '=',need_change_sale_order.id )])
if not order_price:
self.env['order.price'].sudo().create({'sale_order_id':need_change_sale_order.id})
else:
logging.error('同步销售订单价格失败 {}'.format(response.text))
raise UserError('同步销售订单价格失败')
except Exception as e:
raise UserError(e)
def get_page_all_records(self, func, page_size=100):
# 获取模型对象
model = self.env['sale.order'].sudo()
# 初始化分页参数
page_number = 1
while True:
# 计算偏移量
offset = (page_number - 1) * page_size
# 获取当前页的数据
records = model.search([], limit=page_size, offset=offset)
# 如果没有更多记录,退出循环
if not records:
break
# 将当前页的数据添加到结果列表
func(records)
# 增加页码
page_number += 1

View File

@@ -2,6 +2,8 @@
import logging
import json
import base64
import traceback
import requests
from odoo import models
from odoo.exceptions import ValidationError
@@ -73,7 +75,8 @@ class MrStaticResourceDataSync(models.Model):
self.env['sf.feed.per.tooth'].sync_feed_per_tooth_yesterday()
_logger.info("同步刀具物料每齿走刀量完成")
except Exception as e:
logging.info("捕获错误信息:%s" % e)
traceback_error = traceback.format_exc()
logging.error("同步静态资源库失败:%s" % traceback_error)
raise ValidationError("数据错误导致同步失败,请联系管理员")
@@ -2759,8 +2762,9 @@ class CuttingToolBasicParameters(models.Model):
if result['status'] == 1:
if 'basic_parameters_integral_tool' in result['cutting_tool_basic_parameters_yesterday_list']:
if result['cutting_tool_basic_parameters_yesterday_list']['basic_parameters_integral_tool']:
basic_parameters_integral_tool_list = json.loads(
result['cutting_tool_basic_parameters_yesterday_list']['basic_parameters_integral_tool'])
cutting_tool_basic_parameters_yesterday_list= result['cutting_tool_basic_parameters_yesterday_list']
basic_parameters_integral_tool_list = cutting_tool_basic_parameters_yesterday_list['basic_parameters_integral_tool']
if basic_parameters_integral_tool_list:
for integral_tool_item in basic_parameters_integral_tool_list:
integral_tool = self.search(

View File

@@ -1,7 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sf_static_resource_datasync,sf_static_resource_datasync,model_sf_static_resource_datasync,base.group_user,1,1,1,1
access_order_price,order.price,model_order_price,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sf_static_resource_datasync sf_static_resource_datasync model_sf_static_resource_datasync base.group_user 1 1 1 1
3 access_order_price order.price model_order_price base.group_user 1 1 1 1
4
5
6
7

View File

@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.actions.act_window" id="order_price_tree_act">
<field name="name">bfm订单价格对比</field>
<field name="res_model">order.price</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem sequence="22" name="销售订单bfm对比" id="menu_sale_order_bfm_price"
action="order_price_tree_act"
parent="sale.sale_order_menu"
groups="base.group_user"
/>
<record id="view_order_price_tree" model="ir.ui.view">
<field name="name">order.price.list</field>
<field name="model">order.price</field>
<field name="arch" type="xml">
<tree string="订单计划">
<field name="bfm_order_name"/>
<field name="sale_order_name"/>
<field name="sale_order_amount_total"/>
<field name="bfm_amount_total"/>
</tree>
</field>
</record>
</odoo>

View File

@@ -74,28 +74,7 @@
</div>
</div>
</div>
<div>
<h2>AGV参数配置</h2>
<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">
<div class="text-muted">
<label for="agv_rcs_url" string="访问地址"/>
<field name="agv_rcs_url"/>
</div>
<div class="text-muted">
<label for="agv_code" string="车辆编号"/>
<field name="agv_code"/>
</div>
<div class="text-muted">
<label for="wbcode"/>
<field name="wbcode"/>
</div>
</div>
</div>
</div>
</div>
<div>
<h2>中控参数配置</h2>
<div class="row mt16 o_settings_container">
@@ -129,6 +108,21 @@
</div>
</div>
</div>
<div>
<h2>销售订单价格同步</h2>
<div class="row mt16 o_settings_container">
<div class="col-12 col-lg-6 o_setting_box">
<div class="o_setting_left_pane"/>
<div class="o_setting_right_pane">
<div class="col-12 col-lg-6 o_setting_box">
<button type="object" class="oe_highlight" name="sync_sale_price" confirm="确认同步"
string="同步销售订单价格"
/>
</div>
</div>
</div>
</div>
</div>
</xpath>
</field>
</record>

View File

@@ -18,7 +18,8 @@ class sf_production_plan(models.Model):
('draft', '待排程'),
('done', '已排程'),
('processing', '加工中'),
('finished', '已完成')
('finished', '已完成'),
('cancel', '已取消')
], string='状态', tracking=True)
state_order = fields.Integer(compute='_compute_state_order', store=True)
@@ -37,7 +38,7 @@ class sf_production_plan(models.Model):
_order = 'state_order asc, write_date desc'
name = fields.Char(string='制造订单')
# active = fields.Boolean(string='已归档', default=True)
active = fields.Boolean(string='已归档', default=True)
# selected = fields.Boolean(default=False)
# order_number = fields.Char(string='订单号')
order_deadline = fields.Datetime(string='订单交期')
@@ -69,6 +70,32 @@ class sf_production_plan(models.Model):
sequence = fields.Integer(string='序号', copy=False, readonly=True, index=True)
current_operation_name = fields.Char(string='当前工序名称', size=64, default='生产计划')
@api.onchange('date_planned_start')
def date_planned_start_onchange(self):
if self.date_planned_start:
self.date_planned_finished = self.date_planned_start + timedelta(hours=1)
#处理计划状态非待排程,计划结束时间为空的数据处理
def deal_no_date_planned_finished(self):
plans = self.env['sf.production.plan'].search(
[('date_planned_finished', '=', False), ('state', 'in', ['processing', 'done', 'finished'])])
for item in plans:
if item.date_planned_start:
item.date_planned_finished = item.date_planned_start + timedelta(hours=1)
# 处理计划订单截止时间为空的数据
def deal_no_order_deadline(self):
plans = self.env['sf.production.plan'].sudo().search(
[('order_deadline', '=', False)])
for item in plans:
if item.date_planned_start:
item.order_deadline = item.date_planned_start + timedelta(days=7)
@api.model
def search_read(self, domain=None, fields=None, offset=0, limit=None, order=None):
info = super(sf_production_plan, self).search_read(domain, fields, offset, limit, order)
return info
# 计算实际加工时长
@api.depends('actual_start_time', 'actual_end_time')
def _compute_actual_process_time(self):
@@ -199,13 +226,15 @@ class sf_production_plan(models.Model):
if not record.production_line_id:
raise ValidationError("未选择生产线")
else:
is_schedule = self.deal_processing_schedule(record.date_planned_start)
if not is_schedule:
raise ValidationError("排程失败")
workorder_id_list = record.production_id.workorder_ids.ids
if record.production_id:
if record.production_id.workorder_ids:
for item in record.production_id.workorder_ids:
if item.name == 'CNC加工':
item.date_planned_finished = datetime.now() + timedelta(days=100)
# item.date_planned_start = record.date_planned_start
item.date_planned_start = self.date_planned_start if self.date_planned_start else datetime.now()
record.sudo().production_id.plan_start_processing_time = item.date_planned_start
item.date_planned_finished = item.date_planned_start + timedelta(
@@ -218,6 +247,8 @@ class sf_production_plan(models.Model):
record.date_planned_start, record.date_planned_finished = \
item.date_planned_start, item.date_planned_finished
record.state = 'done'
record.date_planned_finished = record.date_planned_start + timedelta(
minutes=60) if not record.date_planned_finished else record.date_planned_finished
# record.production_id.schedule_state = '已排'
record.sudo().production_id.schedule_state = '已排'
record.sudo().production_id.process_state = '待装夹'
@@ -249,6 +280,26 @@ class sf_production_plan(models.Model):
'target': 'current', # 跳转的目标窗口,可以是'current'或'new'
}
# 处理是否可排程
def deal_processing_schedule(self, date_planned_start):
for record in self:
workcenter_ids = record.production_line_id.mrp_workcenter_ids
if not workcenter_ids:
raise UserError('生产线没有配置工作中心')
production_lines = workcenter_ids.filtered(lambda b: "自动生产线" in b.name)
if not production_lines: # 判断是否配置了自动生产线
raise UserError('生产线没有配置自动生产线')
if date_planned_start < datetime.now(): # 判断计划开始时间是否小于当前时间
raise UserError('计划开始时间不能小于当前时间')
if all(not production_line.deal_with_workcenter_calendar(date_planned_start) for production_line in
production_lines): # 判断计划开始时间是否在配置的工作中心的工作日历内
raise UserError('当前计划开始时间不能预约排程,请在工作时间内排程')
if not production_lines.deal_available_default_capacity(date_planned_start): # 判断生产线是否可排程
raise UserError('当前计划开始时间不能预约排程,生产线今日没有可排程的资源')
if not production_lines.deal_available_single_machine_capacity(date_planned_start): # 判断生产线是否可排程
raise UserError('当前计划开始时间不能预约排程,生产线该时间段没有可排程的资源')
return True
def calculate_plan_time_before(self, item, workorder_id_list):
"""
根据CNC工单的时间去计算之前的其他工单的开始结束时间
@@ -361,3 +412,23 @@ class machine_work_schedule(models.Model):
_description = '机台作业计划'
name = fields.Char(string='机台名')
class MrpProductionInheritForPlan(models.Model):
_inherit = 'mrp.production'
def button_cancel(self):
# 调用父类的取消操作
res = super(MrpProductionInheritForPlan, self).button_cancel()
# 更新 sf.production.plan 模型的 state 为 'cancel'
self.env['sf.production.plan'].search([('production_id', '=', self.id)]).write({'state': 'cancel'})
return res
def toggle_active(self):
# 调用父类方法切换 active 状态
res = super(MrpProductionInheritForPlan, self).toggle_active()
self.env['sf.production.plan'].search(
[('production_id', '=', self.id), '|', ('active', '=', False), ('active', '=', True)]).write(
{'active': self.active})
return res

View File

@@ -88,8 +88,8 @@
<group string="加工信息">
<field name="date_planned_start" placeholder="如果不选择计划开始时间,会取当前时间来做排程"/>
<field name="date_planned_finished"/>
<field name="date_planned_start" placeholder="如果不选择计划开始时间,会取当前时间来做排程" required="1"/>
<field name="date_planned_finished" required="1"/>
<field name="actual_process_time"/>
<field name="actual_start_time"/>
<field name="actual_end_time"/>
@@ -291,6 +291,7 @@
<field name="type">ir.actions.act_window</field>
<field name="res_model">mrp.production</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('picking_type_id.active', '=', True)]</field>
</record>
<record model="ir.actions.act_window" id="sale_custom_action">
<!-- 自定义额外的动作 -->

View File

@@ -6,6 +6,7 @@ from datetime import datetime
from odoo import fields, models
# from odoo.exceptions import ValidationError
from odoo.exceptions import UserError
from datetime import datetime, timedelta
_logger = logging.getLogger(__name__)
@@ -14,10 +15,15 @@ class Action_Plan_All_Wizard(models.TransientModel):
_name = 'sf.action.plan.all.wizard'
_description = u'排程向导'
def _get_date_planned_start(self):
planned_start_date = datetime.now() + timedelta(minutes=10)
logging.info('计划开始时间: %s', planned_start_date)
return planned_start_date
# 选择生产线
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)
default=_get_date_planned_start)
# 接收传递过来的计划ID
plan_ids = fields.Many2many('sf.production.plan', string=u'计划ID')

View File

@@ -56,6 +56,27 @@ class QuickEasyOrder(models.Model):
processing_time = fields.Integer('加工时长(min)')
sale_order_id = fields.Many2one('sale.order', '销售订单号')
part_drawing_number = fields.Char('零件图号')
machining_drawings = fields.Binary('2D加工图纸')
machining_drawings_name = fields.Char('2D加工图纸名')
@api.onchange('machining_drawings_name')
def _onchange_machining_drawings_name(self):
for item in self:
if item.machining_drawings_name:
if not item.machining_drawings_name.lower().endswith(
'.pdf'):
raise ValidationError('文件格式上传有误,请检查文件后缀(不区分大小写)是否为pdf')
@api.onchange('parameter_ids')
def _compute_parameter_ids(self):
my_parameter_ids = {}
for item in self:
for item1 in item.parameter_ids:
my_parameter_ids[item1.process_id.id] = item1.ids[0]
my_parameter_ids = list(my_parameter_ids.values())
item.write({'parameter_ids': [(6, 0, my_parameter_ids)]})
@api.depends('unit_price', 'quantity')
def _compute_total_amount(self):
for item in self:
@@ -116,6 +137,10 @@ class QuickEasyOrder(models.Model):
if len(item.upload_model_file) > 1:
raise ValidationError('只允许上传一个文件')
if item.upload_model_file:
if not item.upload_model_file.name.lower().endswith(
'.step') and not item.upload_model_file.name.lower().endswith(
'.stp'):
raise ValidationError('文件格式上传有误,请检查文件后缀(不区分大小写)是否为step、stp')
file_attachment_id = item.upload_model_file[0]
# 附件路径
report_path = file_attachment_id._full_path(file_attachment_id.store_fname)

View File

@@ -184,5 +184,10 @@
</xpath>
</field>
</record>
<record id="purchase.product_normal_action_puchased" model="ir.actions.act_window">
<field name="context">
{"search_default_categ_id":1,"search_default_filter_to_purchase":1, "purchase_product_template": 1}
</field>
</record>
</data>
</odoo>

View File

@@ -73,12 +73,15 @@
<field name="material_model_id" options="{'no_create': True}" required="1"/>
<!-- <field name="process_id"/>-->
<field name="parameter_ids" widget="many2many_tags" string="表面工艺参数"
options="{'no_create': True}" required="1"/>
options="{'no_create': True}"/>
<field name="machining_precision" required="1"/>
<field name="processing_time"/>
<field name="quantity" options="{'format': false}"/>
<field name="unit_price"/>
<field name="price" options="{'format': false}"/>
<field name="part_drawing_number"/>
<field name="machining_drawings" filename="machining_drawings_name" widget="pdf_viewer"/>
<field name="machining_drawings_name" invisible="1"/>
<field name="sale_order_id"
attrs='{"invisible": [("sale_order_id","=",False)],"readonly": [("sale_order_id","!=",False)]}'/>
</group>

View File

@@ -100,8 +100,7 @@
<field name="arch" type="xml">
<field name="property_supplier_payment_term_id" position="before">
<field name="purchase_user_id" context="{'supplier_rank': supplier_rank }"
widget="many2one_avatar_user"
attrs="{'required' : [('supplier_rank','>', 0)]}"/>
widget="many2one_avatar_user"/>
</field>
<xpath expr="//field[@name='property_account_position_id']" position="attributes">
<attribute name="attrs">{'readonly': [('id','!=', False)]}</attribute>

View File

@@ -225,5 +225,10 @@
</field>
</field>
</record>
<record id="sale.product_template_action" model="ir.actions.act_window">
<field name="context">{"search_default_categ_id":1,
"search_default_filter_to_sell":1,"sale_multi_pricelist_product_template": 1}
</field>
</record>
</data>
</odoo>

4
sf_stock/__init__.py Normal file
View File

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

36
sf_stock/__manifest__.py Normal file
View File

@@ -0,0 +1,36 @@
# -*- coding: utf-8 -*-
{
'name': "sf_stock",
'summary': """
处理仓库 -代发货业务""",
'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': ['sf_sale', 'stock'],
# always loaded
'data': [
# 'security/ir.model.access.csv',
'views/stock_picking.xml',
'views/stock_product_template.xml'
],
# only loaded in demonstration mode
'demo': [
'demo/demo.xml',
],
'installable': True,
'application': True,
}

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 SfStock(http.Controller):
# @http.route('/sf_stock/sf_stock', auth='public')
# def index(self, **kw):
# return "Hello, world"
# @http.route('/sf_stock/sf_stock/objects', auth='public')
# def list(self, **kw):
# return http.request.render('sf_stock.listing', {
# 'root': '/sf_stock/sf_stock',
# 'objects': http.request.env['sf_stock.sf_stock'].search([]),
# })
# @http.route('/sf_stock/sf_stock/objects/<model("sf_stock.sf_stock"):obj>', auth='public')
# def object(self, obj, **kw):
# return http.request.render('sf_stock.object', {
# 'object': obj
# })

30
sf_stock/demo/demo.xml Normal file
View File

@@ -0,0 +1,30 @@
<odoo>
<data>
<!--
<record id="object0" model="sf_stock.sf_stock">
<field name="name">Object 0</field>
<field name="value">0</field>
</record>
<record id="object1" model="sf_stock.sf_stock">
<field name="name">Object 1</field>
<field name="value">10</field>
</record>
<record id="object2" model="sf_stock.sf_stock">
<field name="name">Object 2</field>
<field name="value">20</field>
</record>
<record id="object3" model="sf_stock.sf_stock">
<field name="name">Object 3</field>
<field name="value">30</field>
</record>
<record id="object4" model="sf_stock.sf_stock">
<field name="name">Object 4</field>
<field name="value">40</field>
</record>
-->
</data>
</odoo>

View File

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

View File

@@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
import json
import requests
from odoo import models, fields, api
from odoo.exceptions import UserError
import logging
from odoo.tools import date_utils
_logger = logging.getLogger(__name__)
class StockPicking(models.Model):
_inherit = 'stock.picking'
# 重写验证下发发货到bfm
def button_validate(self):
info = super(StockPicking, self).button_validate()
if self.picking_type_code == 'outgoing':
self.send_to_bfm()
return info
def deal_move_ids(self, send_move_ids, send_move_line_ids):
move_ids = [] # 本次发货单
move_line_ids = [] # 本次发货单行
if send_move_ids:
for item in send_move_ids:
val = {
'name': item.product_id.upload_model_file.display_name,
'quantity_done': item.quantity_done,
'date': date_utils.json_default(item.date) if item.date else None,
'description_picking': item.description_picking,
'date_deadline': date_utils.json_default(item.date_deadline) if item.date_deadline else None,
'product_uom_qty': item.product_uom_qty,
'sequence': item.sequence,
'price_unit': item.price_unit,
'priority': item.priority,
'state': item.state,
}
move_ids.append(val)
for item in send_move_line_ids:
val = {
'qty_done': item.qty_done,
'reserved_qty': item.reserved_qty,
'reserved_uom_qty': item.reserved_uom_qty,
'date': date_utils.json_default(item.date) if item.date else None,
'description_picking': item.description_picking,
'state': item.state,
}
move_line_ids.append(val)
return move_ids, move_line_ids
def deal_send_backorder_id(self, backorder_ids1):
backorder_ids = []
if backorder_ids1:
for item in backorder_ids1:
move_ids, move_line_ids = self.deal_move_ids(item.move_ids, item.move_line_ids)
val = {
'receiverName': item.receiverName,
'name': item.sale_id.default_code,
'send_no': item.name,
'scheduled_date': date_utils.json_default(item.scheduled_date) if item.scheduled_date else None,
'date': date_utils.json_default(item.date) if item.date else None,
'date_deadline': date_utils.json_default(item.date_deadline) if item.date_deadline else None,
'date_done': date_utils.json_default(item.date_done) if item.date_done else None,
'move_ids': move_ids,
'move_line_ids': move_line_ids,
'state': item.state,
'move_type': item.move_type,
}
backorder_ids.append(val)
return backorder_ids
def send_to_bfm(self):
skip_backorder = self.env.context.get('skip_backorder')
# 下发发货到bfm
config = self.env['res.config.settings'].get_values()
move_ids, move_line_ids = self.deal_move_ids(self.move_ids, self.move_line_ids)
data = {
'params': {
'receiverName': self.receiverName,
'priority': self.priority,
'name': self.sale_id.default_code,
'send_no': self.name,
'scheduled_date': date_utils.json_default(self.scheduled_date) if self.scheduled_date else None,
'date': date_utils.json_default(self.date) if self.date else None,
'date_deadline': date_utils.json_default(self.date_deadline) if self.date_deadline else None,
'date_done': date_utils.json_default(self.date_done) if self.date_done else None,
'move_ids': move_ids,
'move_line_ids': move_line_ids,
'state': self.state,
'backorder_id': self.deal_send_backorder_id(self.backorder_id),
'backorder_ids': self.deal_send_backorder_id(self.backorder_ids),
'cancel_backorder_ids': skip_backorder,
'move_type': self.move_type,
},
}
url1 = config['bfm_url_new'] + '/api/stock/deliver_goods'
json_str = json.dumps(data)
print('json_str', json_str)
r = requests.post(url1, json=data, data=None)
if r.status_code == 200:
result = json.loads(r.json()['result'])
if result['code'] != 200:
raise UserError(result['message'] or '工厂发货下发bfm失败')
else:
raise UserError('工厂发货下发bfm失败')

View File

@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sf_stock_sf_stock,sf_stock.sf_stock,model_sf_stock_sf_stock,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sf_stock_sf_stock sf_stock.sf_stock model_sf_stock_sf_stock base.group_user 1 1 1 1

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
</data>
</odoo>

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record id="stock.product_template_action_product" model="ir.actions.act_window">
<field name="context">
{"search_default_categ_id":1,"search_default_consumable": 1, 'default_detailed_type': 'product'}
</field>
</record>
</data>
</odoo>

View File

@@ -10,7 +10,7 @@
""",
'category': 'sf',
'website': 'https://www.sf.jikimo.com',
'depends': ['sf_manufacturing'],
'depends': ['sf_manufacturing', 'sf_base'],
'data': [
'security/group_security.xml',
'security/ir.model.access.csv',
@@ -24,6 +24,11 @@
'views/menu_view.xml',
'views/stock.xml',
'data/tool_data.xml',
'wizard/jikimo_bom_wizard.xml',
'views/tool_inventory.xml',
'views/jikimo_bom.xml',
'views/tool_views.xml',
],
'demo': [
],

View File

@@ -122,7 +122,8 @@ class Manufacturing_Connect(http.Controller):
tool_assembly.write({
'after_assembly_tool_loading_length': float(data_list[1] or "0"), # 高度(总长度)
'after_assembly_functional_tool_diameter': float(data_list[2] or "0") * 2, # 直径
'after_assembly_knife_tip_r_angle': float(data_list[3] or "0") # R角
'after_assembly_knife_tip_r_angle': float(data_list[3] or "0"), # R角
'bool_preset_parameter': True
})
except Exception as e:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': e}

View File

@@ -8,4 +8,7 @@ from . import fixture_material_search
from . import fixture_enroll
from . import temporary_data_processing_methods
from . import stock
from . import jikimo_bom
from . import tool_inventory
from . import functional_cutting_tool_model
# from . import product_product

View File

@@ -349,25 +349,100 @@ class CAMWorkOrderProgramKnifePlan(models.Model):
class FunctionalToolAssembly(models.Model):
_name = 'sf.functional.tool.assembly'
_inherit = ['mail.thread']
_inherit = ['mail.thread', 'barcodes.barcode_events_mixin']
_description = '功能刀具组装'
_order = 'assemble_status, use_tool_time asc'
_order = 'tool_loading_time desc, use_tool_time asc'
def on_barcode_scanned(self, barcode):
"""
智能工厂组装单处扫码校验刀具物料
"""
for record in self:
tool_assembly_id = self.env['sf.functional.tool.assembly'].browse(self.ids)
lot_ids = self.env['stock.lot'].sudo().search([('rfid', '=', barcode)])
if lot_ids:
for lot_id in lot_ids:
if lot_id.tool_material_status != '可用':
raise ValidationError(f'Rfid为【{barcode}】的刀柄已被占用,请重新扫描!!')
if lot_id.product_id == record.handle_product_id:
record.handle_code_id = lot_id.id
tool_assembly_id.handle_code_id = lot_id.id
else:
raise ValidationError('刀柄选择错误,请重新确认!!!')
else:
location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', barcode)])
if location:
if location == record.integral_freight_barcode_id:
tool_assembly_id.integral_verify = True
record.integral_verify = True
elif location == record.blade_freight_barcode_id:
tool_assembly_id.blade_verify = True
record.blade_verify = True
elif location == record.bar_freight_barcode_id:
tool_assembly_id.bar_verify = True
record.bar_verify = True
elif location == record.pad_freight_barcode_id:
tool_assembly_id.pad_verify = True
record.pad_verify = True
elif location == record.chuck_freight_barcode_id:
tool_assembly_id.chuck_verify = True
record.chuck_verify = True
else:
raise ValidationError('刀具选择错误,请重新确认!')
else:
raise ValidationError(f'扫描为【{barcode}】的货位不存在,请重新扫描!')
@api.depends('functional_tool_name')
def _compute_name(self):
for obj in self:
obj.name = obj.assembly_order_code
code = fields.Char('功能刀具编码', readonly=True)
rfid = fields.Char('Rfid', readonly=True)
tool_groups_id = fields.Many2one('sf.tool.groups', '刀具组', readonly=True)
tool_groups_id = fields.Many2one('sf.tool.groups', '刀具组', store=True, compute='_compute_inventory_num')
name = fields.Char(string='名称', readonly=True, compute='_compute_name')
assembly_order_code = fields.Char(string='组装单编码', readonly=True)
functional_tool_name_id = fields.Many2one('product.product', string='功能刀具', readonly=True)
functional_tool_name = fields.Char(string='功能刀具名称', readonly=True)
functional_tool_type_id = fields.Many2one('sf.functional.cutting.tool.model', string='功能刀具类型', readonly=True,
group_expand='_read_group_functional_tool_type_ids')
functional_tool_name = fields.Char(string='功能刀具名称', readonly=True, required=True)
tool_inventory_id = fields.Many2one('sf.tool.inventory', string='功能刀具清单', store=True,
compute='_compute_functional_tool_name')
@api.depends('functional_tool_name')
def _compute_functional_tool_name(self):
for item in self:
if item.functional_tool_name:
inventory = self.env['sf.tool.inventory'].sudo().search([('name', '=', item.functional_tool_name)])
if inventory:
item.tool_inventory_id = inventory.id
item.after_assembly_functional_tool_name = item.functional_tool_name # 组装后名称
item.functional_tool_type_id = item.tool_inventory_id.functional_cutting_tool_model_id.id
item.tool_groups_id = item.tool_inventory_id.tool_groups_id.id # 刀具组
item.after_assembly_functional_tool_type_id = item.tool_inventory_id.functional_cutting_tool_model_id.id
item.after_assembly_functional_tool_diameter = item.tool_inventory_id.diameter # 直径
item.after_assembly_knife_tip_r_angle = item.tool_inventory_id.angle # R角
item.after_assembly_tool_loading_length = item.tool_inventory_id.tool_length # 总长度
item.after_assembly_functional_tool_length = item.tool_inventory_id.extension # 伸出长度
item.after_assembly_max_lifetime_value = item.tool_inventory_id.life_span # 最大寿命
@api.depends('tool_inventory_id', 'tool_inventory_id.functional_cutting_tool_model_id', 'tool_inventory_id.angle',
'tool_inventory_id.tool_groups_id', 'tool_inventory_id.diameter', 'tool_inventory_id.tool_length',
'tool_inventory_id.extension', 'tool_inventory_id.life_span')
def _compute_inventory_num(self):
for item in self:
if item.assemble_status != '01':
return True
if item.tool_inventory_id:
item.functional_tool_type_id = item.tool_inventory_id.functional_cutting_tool_model_id.id
item.tool_groups_id = item.tool_inventory_id.tool_groups_id.id # 刀具组
item.after_assembly_functional_tool_type_id = item.tool_inventory_id.functional_cutting_tool_model_id.id
item.after_assembly_functional_tool_diameter = item.tool_inventory_id.diameter # 直径
item.after_assembly_knife_tip_r_angle = item.tool_inventory_id.angle # R角
item.after_assembly_tool_loading_length = item.tool_inventory_id.tool_length # 总长度
item.after_assembly_max_lifetime_value = item.tool_inventory_id.life_span # 最大寿命
functional_tool_type_id = fields.Many2one('sf.functional.cutting.tool.model', string='功能刀具类型',
group_expand='_read_group_functional_tool_type_ids', store=True,
compute='_compute_inventory_num')
functional_tool_diameter = fields.Float(string='功能刀具直径(mm)', readonly=True)
knife_tip_r_angle = fields.Float(string='刀尖R角(mm)', readonly=True)
coarse_middle_thin = fields.Selection([("1", ""), ('2', ''), ('3', '')], string='粗/中/精', readonly=True)
@@ -375,16 +450,17 @@ class FunctionalToolAssembly(models.Model):
tool_loading_length = fields.Float(string='总长度(mm)', readonly=True)
functional_tool_length = fields.Float(string='伸出长(mm)', readonly=True)
effective_length = fields.Float(string='有效长(mm)', readonly=True)
loading_task_source = fields.Selection([('0', 'CAM装刀'), ('1', '机台换刀'), ('2', '按库存组装')],
string='装刀任务来源', readonly=True)
loading_task_source = fields.Selection(
[('0', 'CAM装刀'), ('1', '机台换刀'), ('2', '按库存组装'), ('3', '寿命到期组装')],
string='装刀任务来源', readonly=True)
use_tool_time = fields.Datetime(string='用刀时间', readonly=True)
production_line_name_id = fields.Many2one('sf.production.line', string='申请产线', readonly=True)
machine_tool_name_id = fields.Many2one('maintenance.equipment', string='申请机台', readonly=True)
machine_tool_code = fields.Char(string='机台号', readonly=True)
applicant = fields.Char(string='申请人', readonly=True)
apply_time = fields.Datetime(string='申请时间', default=fields.Datetime.now(), readonly=True)
assemble_status = fields.Selection([('0', '待组装'), ('1', '组装')], string='组装状态', default='0',
tracking=True, readonly=True)
assemble_status = fields.Selection([('0', '待组装'), ('01', '组装'), ('1', '组装'), ('3', '已取消')],
string='组装状态', default='0', tracking=True, readonly=True)
cutter_spacing_code_id = fields.Many2one('maintenance.equipment.tool', string='刀位号', readonly=True)
whether_standard_knife = fields.Boolean(string='是否标准刀', default=True, readonly=True)
reason_for_applying = fields.Char(string='申请原因', readonly=True)
@@ -392,7 +468,7 @@ class FunctionalToolAssembly(models.Model):
alarm_value = fields.Integer(string='报警值(min)', readonly=True)
used_value = fields.Integer(string='已使用值(min)', readonly=True)
image = fields.Binary('图片', readonly=True)
image = fields.Binary('图片', readonly=False)
@api.model
def _read_group_functional_tool_type_ids(self, categories, domain, order):
@@ -402,8 +478,9 @@ class FunctionalToolAssembly(models.Model):
# 刀具物料信息
# ==============整体式刀具型号=============
integral_freight_barcode_id = fields.Many2one('sf.shelf.location', string='整体式刀具货位')
integral_lot_id = fields.Many2one('stock.lot', string='整体式刀具批次')
integral_freight_barcode_id = fields.Many2one('sf.shelf.location', string='整体式刀具货位', readonly=True,
domain="[('product_id.cutting_tool_material_id.name', '=', '整体式刀具'),('product_num', '>', 0)]")
integral_lot_id = fields.Many2one('stock.lot', string='整体式刀具批次', readonly=True)
integral_product_id = fields.Many2one('product.product', string='整体式刀具名称',
compute='_compute_integral_product_id', store=True)
cutting_tool_integral_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='整体式刀具型号',
@@ -412,16 +489,20 @@ class FunctionalToolAssembly(models.Model):
related='integral_product_id.specification_id')
sf_tool_brand_id_1 = fields.Many2one('sf.machine.brand', string='整体式刀具品牌',
related='integral_product_id.brand_id')
integral_verify = fields.Boolean('整体刀校验', default=False)
@api.depends('integral_lot_id')
def _compute_integral_product_id(self):
for item in self:
if item.integral_lot_id:
item.integral_product_id = item.integral_lot_id.product_id.id
else:
item.integral_product_id = False
# =================刀片型号=============
blade_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀片货位')
blade_lot_id = fields.Many2one('stock.lot', string='刀片批次')
blade_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀片货位', readonly=True,
domain="[('product_id.cutting_tool_material_id.name', '=', '刀片'),('product_num', '>', 0)]")
blade_lot_id = fields.Many2one('stock.lot', string='刀片批次', readonly=True)
blade_product_id = fields.Many2one('product.product', string='刀片名称', compute='_compute_blade_product_id',
store=True)
cutting_tool_blade_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀片型号',
@@ -429,16 +510,20 @@ class FunctionalToolAssembly(models.Model):
blade_specification_id = fields.Many2one('sf.tool.materials.basic.parameters', string='刀片规格',
related='blade_product_id.specification_id')
sf_tool_brand_id_2 = fields.Many2one('sf.machine.brand', '刀片品牌', related='blade_product_id.brand_id')
blade_verify = fields.Boolean('刀片校验', default=False)
@api.depends('blade_lot_id')
def _compute_blade_product_id(self):
for item in self:
if item.blade_lot_id:
item.blade_product_id = item.blade_lot_id.product_id.id
else:
item.blade_product_id = False
# ==============刀杆型号================
bar_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀杆货位')
bar_lot_id = fields.Many2one('stock.lot', string='刀杆批次')
bar_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀杆货位', readonly=True,
domain="[('product_id.cutting_tool_material_id.name', '=', '刀杆'),('product_num', '>', 0)]")
bar_lot_id = fields.Many2one('stock.lot', string='刀杆批次', readonly=True)
bar_product_id = fields.Many2one('product.product', string='刀杆名称', compute='_compute_bar_product_id',
store=True)
cutting_tool_cutterbar_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀杆型号',
@@ -446,16 +531,20 @@ class FunctionalToolAssembly(models.Model):
bar_specification_id = fields.Many2one('sf.tool.materials.basic.parameters', string='刀杆规格',
related='bar_product_id.specification_id')
sf_tool_brand_id_3 = fields.Many2one('sf.machine.brand', '刀杆品牌', related='bar_product_id.brand_id')
bar_verify = fields.Boolean('刀杆校验', default=False)
@api.depends('bar_lot_id')
def _compute_bar_product_id(self):
for item in self:
if item.bar_lot_id:
item.bar_product_id = item.bar_lot_id.product_id.id
else:
item.bar_product_id = False
# =============刀盘型号================
pad_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀盘货位')
pad_lot_id = fields.Many2one('stock.lot', string='刀盘批次')
pad_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀盘货位', readonly=True,
domain="[('product_id.cutting_tool_material_id.name', '=', '刀盘'),('product_num', '>', 0)]")
pad_lot_id = fields.Many2one('stock.lot', string='刀盘批次', readonly=True)
pad_product_id = fields.Many2one('product.product', string='刀盘名称', compute='_compute_pad_product_id',
store=True)
cutting_tool_cutterpad_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀盘型号',
@@ -463,37 +552,42 @@ class FunctionalToolAssembly(models.Model):
pad_specification_id = fields.Many2one('sf.tool.materials.basic.parameters', string='刀盘规格',
related='pad_product_id.specification_id')
sf_tool_brand_id_4 = fields.Many2one('sf.machine.brand', '刀盘品牌', related='pad_product_id.brand_id')
pad_verify = fields.Boolean('刀盘校验', default=False)
@api.depends('pad_lot_id')
def _compute_pad_product_id(self):
for item in self:
if item.pad_lot_id:
item.pad_product_id = item.pad_lot_id.product_id.id
else:
item.pad_product_id = False
# ==============刀柄型号==============
handle_freight_rfid = fields.Char('刀柄Rfid', compute='_compute_handle_product_id', store=True)
handle_code_id = fields.Many2one('stock.lot', '刀柄序列号')
handle_product_id = fields.Many2one('product.product', string='刀柄名称', compute='_compute_handle_product_id',
store=True)
handle_code_id = fields.Many2one('stock.lot', '刀柄序列号', readonly=True,
domain="[('product_id', '=', handle_product_id)]")
handle_freight_rfid = fields.Char('刀柄Rfid', compute='_compute_handle_rfid', store=True)
handle_product_id = fields.Many2one('product.product', string='刀柄名称', readonly=True)
cutting_tool_cutterhandle_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀柄型号',
related='handle_product_id.cutting_tool_model_id')
handle_specification_id = fields.Many2one('sf.tool.materials.basic.parameters', string='刀柄规格',
related='handle_product_id.specification_id')
sf_tool_brand_id_5 = fields.Many2one('sf.machine.brand', '刀柄品牌', related='handle_product_id.brand_id')
handle_verify = fields.Boolean('刀柄校验', default=False)
@api.depends('handle_code_id')
def _compute_handle_product_id(self):
def _compute_handle_rfid(self):
for item in self:
if item.handle_code_id:
item.handle_product_id = item.handle_code_id.product_id.id
item.handle_freight_rfid = item.handle_code_id.rfid
item.rfid = item.handle_freight_rfid
else:
item.handle_product_id = False
item.handle_freight_rfid = False
item.rfid = False
# ==============夹头型号==============
chuck_freight_barcode_id = fields.Many2one('sf.shelf.location', string='夹头货位')
chuck_lot_id = fields.Many2one('stock.lot', string='夹头批次')
chuck_freight_barcode_id = fields.Many2one('sf.shelf.location', string='夹头货位', readonly=True,
domain="[('product_id.cutting_tool_material_id.name', '=', '夹头'),('product_num', '>', 0)]")
chuck_lot_id = fields.Many2one('stock.lot', string='夹头批次', readonly=True)
chuck_product_id = fields.Many2one('product.product', string='夹头名称', compute='_compute_chuck_product_id',
store=True)
cutting_tool_cutterhead_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='夹头型号',
@@ -501,12 +595,15 @@ class FunctionalToolAssembly(models.Model):
chuck_specification_id = fields.Many2one('sf.tool.materials.basic.parameters', string='夹头规格',
related='chuck_product_id.specification_id')
sf_tool_brand_id_6 = fields.Many2one('sf.machine.brand', '夹头品牌', related='chuck_product_id.brand_id')
chuck_verify = fields.Boolean('夹头校验', default=False)
@api.depends('chuck_lot_id')
def _compute_chuck_product_id(self):
for item in self:
if item.chuck_lot_id:
item.chuck_product_id = item.chuck_lot_id.product_id.id
else:
item.chuck_product_id = False
# ==================待删除字段==================
integral_freight_barcode = fields.Char('整体式刀具货位')
@@ -525,30 +622,39 @@ class FunctionalToolAssembly(models.Model):
handle_name = fields.Char('')
chuck_code_id = fields.Many2one('stock.lot', '夹头序列号')
chuck_name = fields.Char('')
# ====================暂时无用字段=========================
after_assembly_used_value = fields.Integer(string='组装后已使用值(min)', readonly=True)
# ==============================================
# 组装功能刀具参数信息
start_preset_bool = fields.Boolean('开始预调', default=False)
barcode_id = fields.Many2one('stock.lot', string='功能刀具序列号', readonly=True)
after_assembly_functional_tool_name = fields.Char(string='组装后功能刀具名称', readonly=True)
after_assembly_functional_tool_type_id = fields.Many2one('sf.functional.cutting.tool.model',
string='组装后功能刀具类型', readonly=True)
after_assembly_functional_tool_diameter = fields.Float(string='组装后功能刀具直径(mm)', readonly=True,
digits=(10, 3))
after_assembly_knife_tip_r_angle = fields.Float(string='组装后刀尖R角(mm)', readonly=True, digits=(10, 3))
after_assembly_new_former = fields.Selection([('0', ''), ('1', '')], string='组装后新/旧', readonly=True)
after_assembly_functional_tool_name = fields.Char(string='组装后功能刀具名称', store=True,
compute='_compute_functional_tool_name')
after_assembly_functional_tool_type_id = fields.Many2one('sf.functional.cutting.tool.model', store=True,
string='组装后功能刀具类型',
compute='_compute_inventory_num')
after_assembly_functional_tool_diameter = fields.Float(string='组装后功能刀具直径(mm)', digits=(10, 3), store=True,
compute='_compute_inventory_num')
after_assembly_knife_tip_r_angle = fields.Float(string='组装后刀尖R角(mm)', digits=(10, 3), store=True,
compute='_compute_inventory_num')
after_assembly_new_former = fields.Selection([('0', ''), ('1', '')], string='组装后新/旧', default='0',
store=True, compute='_compute_rota_tive')
cut_time = fields.Integer(string='已切削时间(min)', readonly=True)
cut_length = fields.Float(string='已切削长度(mm)', readonly=True)
cut_number = fields.Integer(string='已切削次数', readonly=True)
after_assembly_whether_standard_knife = fields.Boolean(string='组装后是否标准刀', default=True, readonly=True)
after_assembly_coarse_middle_thin = fields.Selection([("1", ""), ('2', ''), ('3', '')],
string='组装后粗/中/精', readonly=True)
after_assembly_max_lifetime_value = fields.Integer(string='组装后最大寿命值(min)', readonly=True)
after_assembly_alarm_value = fields.Integer(string='组装后报警值(min)', readonly=True)
after_assembly_used_value = fields.Integer(string='组装后已使用值(min)', readonly=True)
after_assembly_tool_loading_length = fields.Float(string='组装后总长度(mm)', readonly=True, digits=(10, 3))
after_assembly_handle_length = fields.Float(string='组装后刀柄长度(mm)', readonly=True, digits=(10, 3))
after_assembly_functional_tool_length = fields.Float(string='组装后伸出长(mm)', readonly=True, digits=(10, 3))
after_assembly_coarse_middle_thin = fields.Selection([("1", ""), ('2', ''), ('3', '')], store=True,
string='组装后粗/中/精', default='3',
compute='_compute_rota_tive')
after_assembly_max_lifetime_value = fields.Integer(string='组装后最大寿命值(min)', store=True,
compute='_compute_inventory_num')
after_assembly_alarm_value = fields.Integer(string='组装后报警值(min)')
after_assembly_tool_loading_length = fields.Float(string='组装后长度(mm)', digits=(10, 3), store=True,
compute='_compute_inventory_num')
after_assembly_handle_length = fields.Float(string='组装后刀柄长度(mm)', digits=(10, 3))
after_assembly_functional_tool_length = fields.Float(string='组装后伸出长(mm)', digits=(10, 3), store=True,
compute='_compute_length')
after_assembly_effective_length = fields.Float(string='组装后有效长(mm)', readonly=True)
L_D_number = fields.Float(string='L/D值(mm)', readonly=True)
hiding_length = fields.Float(string='避空长(mm)', readonly=True)
@@ -562,10 +668,74 @@ class FunctionalToolAssembly(models.Model):
sf_machine_table_tool_changing_apply_id = fields.Many2one('sf.machine.table.tool.changing.apply', '机床换刀申请',
readonly=True)
sf_cam_work_order_program_knife_plan_id = fields.Many2one('sf.cam.work.order.program.knife.plan',
'CAM工单程序用刀计划', readonly=True, )
'CAM工单程序用刀计划', readonly=True)
active = fields.Boolean(string='已归档', default=True)
code = fields.Char('功能刀具编码', compute='_compute_code')
@api.depends('after_assembly_tool_loading_length', 'after_assembly_handle_length')
def _compute_length(self):
for item in self:
if item.after_assembly_tool_loading_length > item.after_assembly_handle_length:
item.after_assembly_functional_tool_length = (
item.after_assembly_tool_loading_length - item.after_assembly_handle_length)
@api.depends('integral_freight_barcode_id', 'blade_freight_barcode_id')
def _compute_rota_tive(self):
for item in self:
rota_tive_boolean = False
if item.integral_freight_barcode_id:
# 整体刀
if item.integral_freight_barcode_id.rotative_Boolean:
rota_tive_boolean = True
elif item.blade_freight_barcode_id:
# 刀片
if item.blade_freight_barcode_id.rotative_Boolean:
rota_tive_boolean = True
if rota_tive_boolean:
item.after_assembly_coarse_middle_thin = '1'
item.after_assembly_new_former = '1'
else:
item.after_assembly_coarse_middle_thin = '3'
item.after_assembly_new_former = '0'
@api.onchange('handle_product_id')
def _onchange_after_assembly_handle_length(self):
for item in self:
if item:
if item.handle_product_id:
item.after_assembly_handle_length = item.handle_product_id.cutting_tool_shank_length
@api.depends('after_assembly_functional_tool_type_id', 'cutting_tool_cutterhandle_model_id',
'after_assembly_functional_tool_diameter', 'after_assembly_tool_loading_length',
'after_assembly_knife_tip_r_angle', 'after_assembly_functional_tool_length',
'after_assembly_handle_length')
def _compute_code(self):
for obj in self:
if obj.cutting_tool_cutterhandle_model_id:
code = obj.cutting_tool_cutterhandle_model_id.code.split('-', 1)[0]
str_1 = '%s-GNDJ-%s-D%sL%sR%sB%sH%s' % (
code, obj.after_assembly_functional_tool_type_id.code, obj.after_assembly_functional_tool_diameter,
obj.after_assembly_tool_loading_length, obj.after_assembly_knife_tip_r_angle,
round(obj.after_assembly_functional_tool_length, 3), obj.after_assembly_handle_length)
obj.code = '%s-%s' % (str_1, self._get_code_1(str_1))
else:
obj.code = ''
def _get_code_1(self, str_2):
functional_tool_assembly = self.env['sf.functional.cutting.tool.entity'].sudo().search(
[('code', 'like', str_2)],
limit=1,
order="id desc"
)
if not functional_tool_assembly:
num = "%03d" % 1
else:
m = int(functional_tool_assembly.code[-3:]) + 1
num = "%03d" % m
return num
def action_open_reference1(self):
self.ensure_one()
return {
@@ -575,41 +745,326 @@ class FunctionalToolAssembly(models.Model):
'res_id': self.id,
}
def put_start_preset(self):
self.search([('start_preset_bool', '=', True)]).write({'start_preset_bool': False})
def start_preset(self):
"""
开始组装
"""
# 设置初始值
self.start_preset_bool = True
self.assemble_status = '01'
self.after_assembly_coarse_middle_thin = '3'
self.after_assembly_new_former = '0'
# 调用功能刀具名称对应的清单的BOM获取对应刀具物料信息
bom = self._get_inventory_bom(self.tool_inventory_id)
# 根据BOM自动配置物料的值
self._set_tool_material(bom)
logging.info('功能刀具开始组装初始化值成功!')
def _set_tool_material(self, bom):
"""根据BOM对刀具物料进行初始配置"""
options = bom.get('options')
# 配置刀柄信息
for handle_id in bom.get('handle_ids'):
if handle_id:
if not self.handle_product_id:
self.handle_product_id = handle_id.id
break
# 刀柄之外的物料配置
if options == '刀柄+整体式刀具':
# 配置整体式刀具
integra_lot_id = self._get_old_tool_material_lot(bom.get('integral_ids'))
integra_location_lot_id = self._get_shelf_location_lot(integra_lot_id)
self.integral_freight_barcode_id = integra_location_lot_id.shelf_location_id.id
self.integral_lot_id = integra_location_lot_id.lot_id.id
else:
# 配置刀片
blade_lot_id = self._get_old_tool_material_lot(bom.get('blade_ids'))
blade_location_lot_id = self._get_shelf_location_lot(blade_lot_id)
self.blade_freight_barcode_id = blade_location_lot_id.shelf_location_id.id
self.blade_lot_id = blade_location_lot_id.lot_id.id
if options == '刀柄+刀杆+刀片':
# 配置刀杆
bar_lot_id = self._get_old_tool_material_lot(bom.get('bar_ids'))
bar_location_lot_id = self._get_shelf_location_lot(bar_lot_id)
self.bar_freight_barcode_id = bar_location_lot_id.shelf_location_id.id
self.bar_lot_id = bar_location_lot_id.lot_id.id
elif options == '刀柄+刀盘+刀片':
# 配置刀盘
pad_lot_id = self._get_old_tool_material_lot(bom.get('pad_ids'))
pad_location_lot_id = self._get_shelf_location_lot(pad_lot_id)
self.pad_freight_barcode_id = pad_location_lot_id.shelf_location_id.id
self.pad_lot_id = pad_location_lot_id.lot_id.id
def _get_old_tool_material_lot(self, material_ids):
""" 根据先进先出原则选择物料批次 """
location_id = self.env['stock.location'].search([('name', '=', '刀具房')])
stock_quant = self.env['stock.quant'].sudo().search(
[('location_id', '=', location_id.id), ('product_id', 'in', material_ids.ids), ('quantity', '>', '0')],
order='lot_id', limit=1)
if stock_quant:
return stock_quant.lot_id
else:
raise ValidationError(f'{material_ids[0].cutting_tool_material_id.name}】物料库存不足,请先进行盘点或采购')
def _get_shelf_location_lot(self, lot_id):
"""根据所给的刀具物料批次号,返回一个刀具物料货位、批次信息"""
location_lots = self.env['sf.shelf.location.lot'].sudo().search([('lot_id', '=', lot_id.id)])
if not location_lots:
raise ValidationError(f'没有查询到批次为【{lot_id.name}】物料的货位信息!')
else:
return location_lots[0]
def _get_inventory_bom(self, inventory_id):
"""获取BOM的刀具物料产品信息"""
product_ids = inventory_id.jikimo_bom_ids.product_ids # BOM配置的物料产品
options = inventory_id.jikimo_bom_ids.options # BOM产品组装类型
if not product_ids or not options:
raise ValidationError('功能刀具清单的BOM未进行配置请先配置BOM信息')
handle_ids = product_ids.filtered(lambda a: a.cutting_tool_material_id.name == '刀柄')
integral_ids = product_ids.filtered(lambda a: a.cutting_tool_material_id.name == '整体式刀具')
blade_ids = product_ids.filtered(lambda a: a.cutting_tool_material_id.name == '刀片')
bar_ids = product_ids.filtered(lambda a: a.cutting_tool_material_id.name == '刀杆')
pad_ids = product_ids.filtered(lambda a: a.cutting_tool_material_id.name == '刀盘')
if not handle_ids:
raise ValidationError('功能刀具清单的BOM未配置[刀柄]信息请先配置BOM再开始组装')
if options == '刀柄+整体式刀具':
if not integral_ids:
raise ValidationError('功能刀具清单的BOM未配置[刀柄]信息请先配置BOM再开始组装')
return {'options': options, 'handle_ids': handle_ids, 'integral_ids': integral_ids}
elif options == '刀柄+刀杆+刀片':
if not blade_ids:
raise ValidationError('功能刀具清单的BOM未配置[刀片]信息请先配置BOM再开始组装')
if not bar_ids:
raise ValidationError('功能刀具清单的BOM未配置[刀杆]信息请先配置BOM再开始组装')
return {'options': options, 'handle_ids': handle_ids, 'blade_ids': blade_ids, 'bar_ids': bar_ids}
elif options == '刀柄+刀盘+刀片':
if not blade_ids:
raise ValidationError('功能刀具清单的BOM未配置[刀片]信息请先配置BOM再开始组装')
if not pad_ids:
raise ValidationError('功能刀具清单的BOM未配置[刀盘]信息请先配置BOM再开始组装')
return {'options': options, 'handle_ids': handle_ids, 'blade_ids': blade_ids, 'pad_ids': pad_ids}
else:
raise ValidationError(f'功能刀具清单BOM的组装方式错误{options}')
def set_tool_lot(self):
# 获取BOM的刀具物料产品信息
tool_data = self._get_inventory_bom(self.tool_inventory_id)
# 获取刀具类型
tool_type = self.env.context.get('tool_type')
if tool_type == '刀柄':
tool_material_ids = tool_data.get('handle_ids')
tool_material_tree_id = self.env.ref('sf_tool_management.view_tool_product_tree')
elif tool_type == '整体式刀具':
tool_material_ids = self._get_all_material_location_lot(tool_data.get('integral_ids'))
tool_material_tree_id = self.env.ref('sf_tool_management.view_shelf_location_lot_tree_1')
elif tool_type == '刀片':
tool_material_ids = self._get_all_material_location_lot(tool_data.get('blade_ids'))
tool_material_tree_id = self.env.ref('sf_tool_management.view_shelf_location_lot_tree_2')
elif tool_type == '刀杆':
tool_material_ids = self._get_all_material_location_lot(tool_data.get('bar_ids'))
tool_material_tree_id = self.env.ref('sf_tool_management.view_shelf_location_lot_tree_3')
else:
tool_material_ids = self._get_all_material_location_lot(tool_data.get('pad_ids'))
tool_material_tree_id = self.env.ref('sf_tool_management.view_shelf_location_lot_tree_4')
if tool_type == '刀柄':
return {
"type": "ir.actions.act_window",
"res_model": "product.product",
"views": [[tool_material_tree_id.id, "tree"],
[self.env.ref('sf_tool_management.view_tool_product_search').id, "search"]],
"target": "new",
"domain": [('id', 'in', tool_material_ids.ids)],
"context": {'tool_id': self.id}
}
elif tool_type in ['整体式刀具', '刀片', '刀杆', '刀盘']:
return {
"type": "ir.actions.act_window",
"res_model": "sf.shelf.location.lot",
"views": [[tool_material_tree_id.id, "tree"]],
"target": "new",
"domain": [('id', 'in', tool_material_ids.ids)],
"context": {'tool_id': self.id}
}
def _get_all_material_location_lot(self, material_ids):
""" 获取所有满足条件 """
location_id = self.env['stock.location'].search([('name', '=', '刀具房')])
stock_quant_ids = self.env['stock.quant'].sudo().search(
[('location_id', '=', location_id.id), ('product_id', 'in', material_ids.ids if material_ids else []),
('quantity', '>', '0')])
lot_ids = []
for stock_quant_id in stock_quant_ids:
lot_ids.append(stock_quant_id.lot_id.id)
location_lots = self.env['sf.shelf.location.lot'].sudo().search([('lot_id', 'in', lot_ids)])
return location_lots
def functional_tool_assembly(self):
"""
功能刀具确认组装
:return:
"""
logging.info('功能刀具开始组装!')
# 对物料做必填判断
self.materials_must_be_judged()
product_id = self.env['product.product']
# 创建组装入库单
# 创建功能刀具批次/序列号记录
stock_lot = product_id.create_assemble_warehouse_receipt(self)
# 封装功能刀具数据,用于创建功能刀具记录
desc_2 = self.get_desc(stock_lot, self)
# 创建功能刀具组装入库单
self.env['stock.picking'].create_tool_stocking_picking(stock_lot, self)
# 创建刀具物料出库单
self.env['stock.picking'].create_tool_stocking_picking1(self)
# ============================创建功能刀具列表、安全库存记录===============================
# 创建功能刀具列表记录
record_1 = self.env['sf.functional.cutting.tool.entity'].create(desc_2)
# 创建安全库存信息
self.env['sf.real.time.distribution.of.functional.tools'].create_or_edit_safety_stock({
'functional_name_id': self.tool_inventory_id.id
}, record_1)
# =====================修改功能刀具组装单、机床换刀申请、CAM工单程序用刀计划的状态==============
if self.sf_machine_table_tool_changing_apply_id:
# 修改机床换刀申请的状态
self.env['sf.machine.table.tool.changing.apply'].sudo().search([
('id', '=', self.sf_machine_table_tool_changing_apply_id.id)
]).write({'status': '3'})
elif self.sf_cam_work_order_program_knife_plan_id:
# 修改CAM工单程序用刀计划状态
cam_plan = self.env['sf.cam.work.order.program.knife.plan'].sudo().search([
('id', '=', self.sf_cam_work_order_program_knife_plan_id.id)
])
cam_plan.write({'plan_execute_status': '2'})
# ============修改组装单状态为已组装=================================
self.write({
'after_assembly_tool_loading_length': 0,
'after_assembly_functional_tool_diameter': 0,
'after_assembly_knife_tip_r_angle': 0,
'start_preset_bool': True
'assemble_status': '1',
'start_preset_bool': False,
'tool_loading_person': self.env.user.name,
'tool_loading_time': fields.Datetime.now()
})
logging.info('功能刀具组装完成!')
def materials_must_be_judged(self):
"""
功能刀具组装必填判断
"""
# 物料准确性校验
# 物料必填校验
if not self.handle_code_id:
raise ValidationError('缺少【刀柄】物料信息!')
if self.integral_lot_id:
if not self.integral_verify:
raise ValidationError('【整体式刀具】未进行验证!')
elif self.blade_lot_id:
if not self.blade_verify:
raise ValidationError('【刀片】未进行验证!')
if self.bar_lot_id:
if not self.bar_verify:
raise ValidationError('【刀杆】未进行验证!')
elif self.pad_lot_id:
if not self.pad_verify:
raise ValidationError('【刀盘】未进行验证!')
# 组装参数必填校验
if self.after_assembly_max_lifetime_value == 0:
raise ValidationError('组装参数信息【最大寿命值】不能为0')
if self.after_assembly_functional_tool_diameter <= 0:
raise ValidationError('组装参数信息【刀具直径】不能小于等于0')
if self.after_assembly_tool_loading_length == 0:
raise ValidationError('组装参数信息【总长度】不能为0')
if self.after_assembly_handle_length == 0:
raise ValidationError('组装参数信息【刀柄长度】不能为0')
if self.after_assembly_tool_loading_length < self.after_assembly_handle_length:
raise ValidationError('组装参数信息【刀柄长度】不能大于【总长度】!')
def get_desc(self, stock_lot, functional_tool_assembly_id):
return {
'type': 'ir.actions.act_window',
'res_model': 'sf.functional.tool.assembly.order',
'name': '功能刀具组装单',
'view_mode': 'form',
'target': 'new',
'context': {'default_name': self.name,
'default_assembly_order_code': self.assembly_order_code,
'default_production_line_name_id': self.production_line_name_id.id,
'default_machine_tool_name_id': self.machine_tool_name_id.id,
'default_cutter_spacing_code_id': self.cutter_spacing_code_id.id,
'default_functional_tool_name': self.functional_tool_name,
'default_functional_tool_type_id': self.functional_tool_type_id.id,
'default_tool_groups_id': self.tool_groups_id.id,
'default_functional_tool_diameter': self.functional_tool_diameter,
'default_knife_tip_r_angle': self.knife_tip_r_angle,
'default_tool_loading_length': self.tool_loading_length,
'default_functional_tool_length': self.functional_tool_length,
'default_effective_length': self.effective_length,
'default_whether_standard_knife': self.whether_standard_knife,
'default_coarse_middle_thin': self.coarse_middle_thin,
'default_new_former': self.new_former,
'default_use_tool_time': self.use_tool_time,
'default_reason_for_applying': self.reason_for_applying
}
'barcode_id': stock_lot.id,
'code': self.code,
'name': self.tool_inventory_id.name,
'tool_name_id': self.tool_inventory_id.id,
'rfid': self.rfid,
'tool_groups_id': self.tool_groups_id.id,
'functional_tool_name_id': functional_tool_assembly_id.id,
'sf_cutting_tool_type_id': self.after_assembly_functional_tool_type_id.id,
'cutting_tool_integral_model_id': self.integral_product_id.id,
'cutting_tool_blade_model_id': self.blade_product_id.id,
'cutting_tool_cutterbar_model_id': self.bar_product_id.id,
'cutting_tool_cutterpad_model_id': self.pad_product_id.id,
'cutting_tool_cutterhandle_model_id': self.handle_product_id.id,
'cutting_tool_cutterhead_model_id': self.chuck_product_id.id,
'functional_tool_diameter': self.after_assembly_functional_tool_diameter,
'knife_tip_r_angle': self.after_assembly_knife_tip_r_angle,
'coarse_middle_thin': self.after_assembly_coarse_middle_thin,
'new_former': self.after_assembly_new_former,
'tool_loading_length': self.after_assembly_tool_loading_length,
'handle_length': self.after_assembly_handle_length,
'functional_tool_length': self.after_assembly_functional_tool_length,
'effective_length': self.after_assembly_effective_length,
'max_lifetime_value': self.after_assembly_max_lifetime_value,
'alarm_value': self.after_assembly_alarm_value,
'used_value': self.after_assembly_used_value,
'whether_standard_knife': self.after_assembly_whether_standard_knife,
'L_D_number': self.L_D_number,
'hiding_length': self.hiding_length,
'cut_time': self.cut_time,
'cut_length': self.cut_length,
'cut_number': self.cut_number,
'image': self.image,
}
# def put_start_preset(self):
# # 打开组装弹窗开始组装
# self.search([('start_preset_bool', '=', True)]).write({'start_preset_bool': False})
# self.write({
# 'after_assembly_tool_loading_length': 0,
# 'after_assembly_functional_tool_diameter': 0,
# 'after_assembly_knife_tip_r_angle': 0,
# 'start_preset_bool': True
# })
# return {
# 'type': 'ir.actions.act_window',
# 'res_model': 'sf.functional.tool.assembly.order',
# 'name': '功能刀具组装单',
# 'view_mode': 'form',
# 'target': 'new',
# 'context': {'default_name': self.name,
# 'default_assembly_order_code': self.assembly_order_code,
# 'default_production_line_name_id': self.production_line_name_id.id,
# 'default_machine_tool_name_id': self.machine_tool_name_id.id,
# 'default_cutter_spacing_code_id': self.cutter_spacing_code_id.id,
# 'default_functional_tool_name': self.functional_tool_name,
# 'default_functional_tool_type_id': self.functional_tool_type_id.id,
# 'default_tool_groups_id': self.tool_groups_id.id,
# 'default_functional_tool_diameter': self.functional_tool_diameter,
# 'default_knife_tip_r_angle': self.knife_tip_r_angle,
# 'default_tool_loading_length': self.tool_loading_length,
# 'default_functional_tool_length': self.functional_tool_length,
# 'default_effective_length': self.effective_length,
# 'default_whether_standard_knife': self.whether_standard_knife,
# 'default_coarse_middle_thin': self.coarse_middle_thin,
# 'default_new_former': self.new_former,
# 'default_use_tool_time': self.use_tool_time,
# 'default_reason_for_applying': self.reason_for_applying
# }
# }
def _get_code(self, loading_task_source):
"""
自动生成组装单编码
@@ -622,6 +1077,8 @@ class FunctionalToolAssembly(models.Model):
code = 'J' + datetime
elif loading_task_source == '2':
code = 'K' + datetime
elif loading_task_source == '3':
code = 'S' + datetime
else:
code = False
functional_tool_assembly = self.env['sf.functional.tool.assembly'].sudo().search(
@@ -648,6 +1105,13 @@ class FunctionalToolAssembly(models.Model):
return functional_tool
return False
bool_preset_parameter = fields.Boolean('', default=False)
def get_tool_preset_parameter(self):
if not self.bool_preset_parameter:
raise ValidationError('没有获取到测量数据, 请确认刀具预调仪操作是否正确!')
return True
def assemble_single_print(self):
"""
todo 组装单打印
@@ -1032,6 +1496,47 @@ class FunctionalToolDismantle(models.Model):
})
logging.info('%s】刀具拆解成功!' % self.name)
# ==================根据条件创建功能刀具组装单=======================
# 如果报废原因为【寿命到期报废】并且刀柄不报废时, 创建功能刀具组装单
if self.dismantle_cause in ['寿命到期报废'] and not self.scrap_boolean:
# 创建组装单
assembly_id = self.env['sf.functional.tool.assembly'].sudo().create({
'functional_tool_name': self.functional_tool_id.name,
'handle_code_id': self.handle_lot_id.id,
'handle_product_id': self.handle_product_id.id,
'loading_task_source': '3',
'use_tool_time': fields.Datetime.now() + timedelta(hours=4),
'reason_for_applying': '刀具寿命到期'
})
return {
'type': 'ir.actions.act_window',
'res_model': 'sf.functional.tool.assembly',
'view_type': 'form',
'view_mode': 'form',
'res_id': assembly_id.id,
# 'target': 'new'
}
# {
# 'type': 'ir.actions.client',
# 'tag': 'display_notification',
# 'params': {
# 'title': '组装单创建完成',
# 'message': '请组装同名称的功能刀具',
# 'type': 'info'
# }
# }
# 'params': {
# 'title': _('The following replenishment order has been generated'),
# 'message': '%s',
# 'links': [{
# 'label': order.display_name,
# 'url': f'#action={action.id}&id={order.id}&model=purchase.order',
# }],
# 'sticky': False,
# }
def create_tool_picking_scrap(self, datas):
scrap_data = datas['scrap']
picking_data = datas['picking']

View File

@@ -0,0 +1,6 @@
from odoo import models, fields
class SyncFunctionalCuttingToolModel(models.Model):
_inherit = 'sf.functional.cutting.tool.model'
cutting_tool_type_ids = fields.Many2many('sf.cutting.tool.type', string='适用刀具物料类型')

View File

@@ -50,7 +50,7 @@ class ToolDatasync(models.Model):
# self.env['sf.real.time.distribution.of.functional.tools'].sudo().sync_enroll_functional_tool_real_time_distribution_all()
# logging.info("功能刀具安全库存每日同步成功")
except Exception as e:
logging.info("捕获错误信息:%s" % e)
logging.info("刀具物料、刀具信息同步失败:%s" % e)
raise ValidationError("数据错误导致同步失败,请联系管理员")
@@ -312,7 +312,7 @@ class FunctionalToolWarning(models.Model):
else:
logging.info('没有注册功能刀具预警信息')
except Exception as e:
logging.info("捕获错误信息:%s" % e)
logging.info("功能刀具预警同步失败:%s" % e)
class StockMoveLine(models.Model):
@@ -373,7 +373,7 @@ class StockMoveLine(models.Model):
else:
logging.info('没有注册功能刀具出入库记录信息')
except Exception as e:
logging.info("捕获错误信息:%s" % e)
logging.info("出入库记录信息同步失败:%s" % e)
class RealTimeDistributionFunctionalTools(models.Model):
@@ -446,4 +446,4 @@ class RealTimeDistributionFunctionalTools(models.Model):
else:
logging.info('没有注册功能刀具出入库记录信息')
except Exception as e:
logging.info("捕获错误信息:%s" % e)
logging.info("实时功能刀具同步失败:%s" % e)

View File

@@ -0,0 +1,156 @@
# -*- coding: utf-8 -*-
from collections import Counter
from xml import etree
from odoo import models, fields, api, Command
from odoo.exceptions import UserError, AccessError
from odoo.http import request
class jikimo_bom(models.Model):
_name = 'jikimo.bom'
_description = '功能刀具物料清单'
tool_inventory_id = fields.Many2one('sf.tool.inventory', '功能刀具清单')
tool_name = fields.Char(related="tool_inventory_id.name", string='功能刀具名称')
functional_cutting_tool_model_id = fields.Many2one(related='tool_inventory_id.functional_cutting_tool_model_id',
string='功能刀具类型')
tool_groups_id = fields.Many2one(related='tool_inventory_id.tool_groups_id', string='刀具组')
tool_length = fields.Float(related='tool_inventory_id.tool_length', string='刀具总长(mm)')
diameter = fields.Float(related='tool_inventory_id.diameter', string='直径(mm)')
angle = fields.Float(related='tool_inventory_id.angle', string='R角(mm)')
extension = fields.Float(related='tool_inventory_id.extension', string='伸出长度(mm)')
product_ids = fields.Many2many('product.product', string='产品')
knife_handle_model = fields.Selection(related='tool_inventory_id.knife_handle_model', string='使用刀柄型号')
options = fields.Char('产品清单')
def name_get(self):
result = []
for bom in self:
result.append((bom.id, '功能刀具物料清单'))
return result
def check_types_in_list(self):
"""
检查产品列表中的元素是否包含了所有指定的类型,并且每种类型至少出现一次。
:return: 如果条件满足返回True否则返回False
"""
if not self.product_ids:
return False
try:
# 统计每个类型的出现次数
type_counts = Counter(item.cutting_tool_material_id.name for item in self.product_ids)
# 检查是否每种类型的出现次数都大于0并且类型的数量与选项字符串中的数量相等
return all(count > 0 for count in type_counts.values()) and len(type_counts) == len(self.options.split('+'))
except AttributeError:
# 如果出现属性错误,说明产品列表中的元素可能缺少必要的属性
return False
# type_counts = Counter(item.cutting_tool_material_id.name for item in self.product_ids)
# return all(count > 0 for count in type_counts.values()) and len(type_counts) == self.options.split('+')
def write(self, vals):
# 在更新模型时记录旧的 Many2many ID 列表
if 'product_ids' in vals and not self.env.context.get('is_assembly_options'):
old_product_counter = Counter(self.product_ids.ids)
super(jikimo_bom, self).write(vals)
new_product_counter = Counter(self.product_ids.ids)
delete_product_counter = old_product_counter - new_product_counter
if delete_product_counter:
# 删除操作
if self.check_types_in_list():
return True
else:
raise UserError('每种物料最少要有一个')
return True
return super(jikimo_bom, self).write(vals)
def bom_product_domains(self, assembly_options):
"""
根据装配选项生成产品域列表
:param assembly_options: 装配选项字符串,各选项以'+'分隔
:return: 动态生成的产品搜索条件
"""
self.options = assembly_options
cutting_tool_materials = self.env['sf.cutting.tool.material'].search(
[('name', 'in', assembly_options.split('+'))])
domains = []
for index, option in enumerate(cutting_tool_materials):
domain = ['&', ('cutting_tool_material_id', '=', option.id),
("cutting_tool_type_id", "in",
self.tool_inventory_id.functional_cutting_tool_model_id.cutting_tool_type_ids.ids)]
if option.name == '刀柄':
domain = ['&'] + domain + [
("cutting_tool_taper_shank_model", "=", self.tool_inventory_id.knife_handle_model)]
if option.name == '整体式刀具':
domain = ['&'] + domain + [
'|',
# 刀具直径
('cutting_tool_blade_diameter', '=', self.tool_inventory_id.diameter),
# r角
('cutting_tool_blade_tip_working_size', '=', self.tool_inventory_id.angle)]
if option.name == '刀杆':
domain = ['&'] + domain + [
("cutting_tool_cutter_arbor_diameter", "=", self.tool_inventory_id.diameter)]
if option.name == '刀片':
domain = ['&'] + domain + [
("cutting_tool_blade_tip_circular_arc_radius", "=", self.tool_inventory_id.angle)]
if option.name == '刀盘':
domain = ['&'] + domain + [
("cutting_tool_cutter_head_diameter", "=", self.tool_inventory_id.diameter)]
domains = domains + domain
if index != 0:
domains = ['|'] + domains
domains = domains + [('stock_move_ids', '!=',False)]
return domains
def generate_bill_materials(self, assembly_options):
"""
生成物料清单
根据装配选项生成物料清单首先获取产品领域然后搜索相关产品并设置产品ID。
:param assembly_options: 组装方式
:type assembly_options: 装配选项字符串,各选项以'+'分隔
"""
domains = self.bom_product_domains(assembly_options)
products = self.env['product.product'].search(domains)
if products:
new_context = dict(self.env.context)
new_context['is_assembly_options'] = True
self.with_context(new_context).write({'product_ids': [Command.set(products.ids)]})
# self.product_ids = [Command.set(products.ids)]
class jikimo_bom_line(models.Model):
_name = 'jikimo.bom.line'
_description = 'jikimo.bom.line'
name = fields.Char()
class ProductProduct(models.Model):
_inherit = 'product.product'
_order = 'cutting_tool_material_id, cutting_tool_type_id'
# stock_move_count = fields.Integer(string='stock_move count', compute='_compute_stock_move_count')
#
# @api.depends('stock_move_ids')
# def _compute_stock_move_count(self):
# for record in self:
# if record.stock_move_ids:
# record.stock_move_count = len(record.stock_move_ids)
# else:
# record.stock_move_count = 0
def search(self, args, offset=0, limit=None, order=None, count=False):
# 你可以在这里修改 `args` 以调整搜索条件
# 例如,添加额外的搜索条件
if self.env.context.get('jikimo_bom_product'):
bom_id = self.env['jikimo.bom'].browse(request.session.get('jikimo_bom_product').get('bom_id'))
if not bom_id.options:
raise UserError('请先选择组装方式')
domains = bom_id.bom_product_domains(bom_id.options)
args = args + domains
return super(ProductProduct, self).search(args, offset=offset, limit=limit, order=order, count=count)

View File

@@ -1,4 +1,9 @@
import logging
from datetime import timedelta, datetime, date
from odoo import api, fields, models, _
from odoo.exceptions import ValidationError
class ShelfLocation(models.Model):
@@ -58,3 +63,289 @@ class StockPicking(models.Model):
if move_lines:
self.env['stock.move.line'].sudo().button_function_tool_use_verify(move_lines)
return res
def create_tool_stocking_picking(self, stock_lot, obj):
"""
创建功能刀具组装入库单
"""
# 获取名称为刀具组装入库的作业类型
picking_type_id = self.env['stock.picking.type'].sudo().search([('name', '=', '刀具组装入库')])
# 创建刀具组装入库单
picking_id = self.env['stock.picking'].create({
'name': self._get_name_stock(picking_type_id),
'picking_type_id': picking_type_id.id,
'location_id': picking_type_id.default_location_src_id.id,
'location_dest_id': picking_type_id.default_location_dest_id.id,
'origin': obj.assembly_order_code
})
# 创建作业详情对象记录,并绑定到刀具组装入库单
self.env['stock.move.line'].create({
'picking_id': picking_id.id,
'product_id': stock_lot.product_id.id,
'location_id': picking_id.location_id.id,
'location_dest_id': picking_id.location_dest_id.id,
'lot_id': stock_lot.id,
'install_tool_time': fields.Datetime.now(),
'qty_done': 1,
'functional_tool_name_id': obj.id,
'functional_tool_type_id': obj.functional_tool_type_id.id,
'diameter': obj.after_assembly_functional_tool_diameter,
'knife_tip_r_angle': obj.after_assembly_knife_tip_r_angle,
'code': obj.code,
'rfid': obj.rfid,
'functional_tool_name': obj.after_assembly_functional_tool_name,
'tool_groups_id': obj.tool_groups_id.id
})
# 将刀具组装入库单的状态更改为就绪
picking_id.action_confirm()
picking_id.button_validate()
def _get_name_stock(self, picking_type_id):
name = picking_type_id.sequence_id.prefix + str(
datetime.strptime(str(fields.Date.today()), "%Y-%m-%d").strftime("%Y%m%d"))
stock_id = self.env['stock.picking'].sudo().search(
[('name', 'like', name), ('picking_type_id', '=', picking_type_id.id)],
limit=1,
order="id desc"
)
if not stock_id:
num = "%03d" % 1
else:
m = int(stock_id.name[-3:]) + 1
num = "%03d" % m
return name + str(num)
def tool_location_num(self, freight_barcode_id, lot_id):
location_lot = self.env['sf.shelf.location.lot'].sudo().search([('lot_id', '=', lot_id.id), (
'shelf_location_id', '=', freight_barcode_id.id)])
if not location_lot:
raise ValidationError(
f'[{freight_barcode_id.barcode}]货位的[{lot_id.name}]批次物料已用完,请重新选择!')
def create_tool_stocking_picking1(self, obj):
"""
创建刀具物料出库单
"""
# 获取名称为内部调拨的作业类型
picking_type_id = self.env['stock.picking.type'].sudo().search([('name', '=', '内部调拨')])
# 创建刀具物料出库单
picking_id = self.env['stock.picking'].create({
'name': self._get_name_stock1(picking_type_id),
'picking_type_id': picking_type_id.id,
'location_id': self.env['stock.location'].search([('name', '=', '刀具房')]).id,
'location_dest_id': self.env['stock.location'].search([('name', '=', '刀具组装位置')]).id,
'origin': obj.assembly_order_code
})
# =============刀具物料出库===================
stock_move_id = self.env['stock.move']
datas = {'data': [], 'picking_id': picking_id}
if obj.handle_code_id:
if obj.handle_code_id.tool_material_status == '在用':
raise ValidationError(f'Rfid为{obj.handle_code_id.rfid}的刀柄已被使用,请重新选择!')
# 修改刀柄序列号状态为【在用】
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:
self.tool_location_num(obj.integral_freight_barcode_id, obj.integral_lot_id)
datas['data'].append(
{'current_location_id': obj.integral_freight_barcode_id, 'lot_id': obj.integral_lot_id})
if obj.blade_product_id:
self.tool_location_num(obj.blade_freight_barcode_id, obj.blade_lot_id)
datas['data'].append(
{'current_location_id': obj.blade_freight_barcode_id, 'lot_id': obj.blade_lot_id})
if obj.bar_product_id:
self.tool_location_num(obj.bar_freight_barcode_id, obj.bar_lot_id)
datas['data'].append(
{'current_location_id': obj.bar_freight_barcode_id, 'lot_id': obj.bar_lot_id})
if obj.pad_product_id:
self.tool_location_num(obj.pad_freight_barcode_id, obj.pad_lot_id)
datas['data'].append(
{'current_location_id': obj.pad_freight_barcode_id, 'lot_id': obj.pad_lot_id})
if obj.chuck_product_id:
self.tool_location_num(obj.chuck_freight_barcode_id, obj.chuck_lot_id)
datas['data'].append(
{'current_location_id': obj.chuck_freight_barcode_id, 'lot_id': obj.chuck_lot_id})
# 创建刀具物料出库库存移动记录
stock_move_id.create_tool_material_stock_moves(datas)
# 将刀具物料出库库单的状态更改为就绪
picking_id.action_confirm()
# 修改刀具物料出库移动历史记录
stock_move_id.write_tool_material_stock_move_lines(datas)
# 设置数量,并验证完成
picking_id.action_set_quantities_to_reservation()
picking_id.button_validate()
logging.info(f'刀具物料调拨单状态:{picking_id.state}')
def _get_name_stock1(self, picking_type_id):
name = f'{picking_type_id.sequence_id.prefix}DJ/{date.today().strftime("%y")}'
stock_id = self.env['stock.picking'].sudo().search(
[('name', 'like', name), ('picking_type_id', '=', picking_type_id.id)],
limit=1,
order="id desc"
)
if not stock_id:
num = "%05d" % 1
else:
m = int(stock_id.name[-5:]) + 1
num = "%05d" % m
return name + str(num)
class StockMove(models.Model):
_inherit = 'stock.move'
def create_tool_material_stock_moves(self, datas):
picking_id = datas['picking_id']
data = datas['data']
stock_move_ids = []
for res in data:
if res:
if res['lot_id'].product_qty <= 0:
raise ValidationError(
f'[{res["lot_id"].product_id.name}产品的{res["lot_id"].name}]批次/序列号库存不足!')
# 创建库存移动记录
stock_move_id = self.env['stock.move'].sudo().create({
'name': picking_id.name,
'picking_id': picking_id.id,
'product_id': res['lot_id'].product_id.id,
'location_id': picking_id.location_id.id,
'location_dest_id': picking_id.location_dest_id.id,
'product_uom_qty': 1.00,
'reserved_availability': 1.00
})
stock_move_ids.append(stock_move_id)
return stock_move_ids
def write_tool_material_stock_move_lines(self, datas):
picking_id = datas['picking_id']
data = datas['data']
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:
move_line_id.write({
'current_location_id': res.get('current_location_id').id,
'lot_id': res.get('lot_id').id
})
return True
class ProductProduct(models.Model):
_inherit = 'product.product'
def create_assemble_warehouse_receipt(self, obj):
"""
创建功能刀具批次/序列号记录
"""
product_id = self.env['product.product'].search([('categ_type', '=', '功能刀具'), ('tracking', '=', 'serial')])
if not product_id:
logging.info('没有搜索到功能刀具产品:%s' % product_id)
raise ValidationError('没有找到按唯一序列号追溯的功能刀具产品信息!')
stock_lot = self.env['stock.lot'].create({
'name': self.get_stock_lot_name(obj),
'product_id': product_id[0].id,
'company_id': self.env.company.id
})
return stock_lot
def get_stock_lot_name(self, obj):
"""
生成功能刀具序列号
"""
company = obj.cutting_tool_cutterhandle_model_id.code.split('-', 1)[0]
new_time = datetime.strptime(str(fields.Date.today()), "%Y-%m-%d").strftime("%Y%m%d")
code = '%s-GNDJ-%s-%s' % (company, obj.after_assembly_functional_tool_type_id.code, new_time)
stock_lot_id = self.env['stock.lot'].sudo().search(
[('name', 'like', code)], limit=1, order="id desc")
if not stock_lot_id:
num = "%03d" % 1
else:
m = int(stock_lot_id.name[-3:]) + 1
num = "%03d" % m
return '%s-%s' % (code, num)
def set_tool_material(self):
tool_id = self.env.context.get('tool_id')
tool_assembly_id = self.env['sf.functional.tool.assembly'].sudo().search([('id', '=', tool_id)])
if len(self) > 1:
raise ValidationError('请不要多选')
else:
tool_assembly_id.handle_product_id = self.id
tool_assembly_id.handle_code_id = False
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'message': '刀柄信息更改成功',
'type': 'success',
'next': {'type': 'ir.actions.act_window_close'}
}
}
class SfShelfLocationLot(models.Model):
_inherit = 'sf.shelf.location.lot'
product_id = fields.Many2one('product.product', '产品', compute='_compute_product_id', store=True)
cutting_tool_type = fields.Char(string="刀具物料类型", compute='_compute_product_id', store=True)
cutting_tool_type_id = fields.Many2one('sf.cutting.tool.type', string='类型',
related='product_id.cutting_tool_type_id')
cutting_tool_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='型号名称',
related='product_id.cutting_tool_model_id')
specification_id = fields.Many2one('sf.tool.materials.basic.parameters', string='物料号',
related='product_id.specification_id')
brand_id = fields.Many2one('sf.machine.brand', '品牌', related='product_id.brand_id')
cutting_tool_blade_diameter = fields.Float('刃部直径(mm)', related='product_id.cutting_tool_blade_diameter')
cutting_tool_blade_tip_working_size = fields.Char('刀尖R角(mm)',
related='product_id.cutting_tool_blade_tip_working_size')
cutting_tool_blade_radius = fields.Char('刀尖圆弧半径(mm)',
related='product_id.cutting_tool_blade_tip_circular_arc_radius')
cutting_tool_cutter_arbor_diameter = fields.Float('刀杆直径(mm)',
related='product_id.cutting_tool_cutter_arbor_diameter')
cutting_tool_cutter_head_diameter = fields.Float('刀盘直径(mm)',
related='product_id.cutting_tool_cutter_head_diameter')
fit_blade_shape_id = fields.Many2one('maintenance.equipment.image', '适配刀片形状',
related='product_id.fit_blade_shape_id')
@api.depends('lot_id')
def _compute_product_id(self):
for item in self:
if item.lot_id:
item.product_id = item.lot_id.product_id.id
item.cutting_tool_type = item.lot_id.product_id.cutting_tool_type
def set_tool_material(self):
tool_type = self.env.context.get('tool_type')
tool_id = self.env.context.get('tool_id')
tool_assembly_id = self.env['sf.functional.tool.assembly'].sudo().search([('id', '=', tool_id)])
if len(self) > 1:
raise ValidationError('请不要多选')
if tool_type == '整体式刀具':
tool_assembly_id.integral_freight_barcode_id = self.shelf_location_id.id
tool_assembly_id.integral_lot_id = self.lot_id.id
tool_assembly_id.integral_verify = False
elif tool_type == '刀片':
tool_assembly_id.blade_freight_barcode_id = self.shelf_location_id.id
tool_assembly_id.blade_lot_id = self.lot_id.id
tool_assembly_id.blade_verify = False
elif tool_type == '刀杆':
tool_assembly_id.bar_freight_barcode_id = self.shelf_location_id.id
tool_assembly_id.bar_lot_id = self.lot_id.id
tool_assembly_id.bar_verify = False
elif tool_type == '刀盘':
tool_assembly_id.pad_freight_barcode_id = self.shelf_location_id.id
tool_assembly_id.pad_lot_id = self.lot_id.id
tool_assembly_id.pad_verify = False
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'message': f'[{tool_type}]物料信息更改成功',
'type': 'success',
'next': {'type': 'ir.actions.act_window_close'}
}
}

View File

@@ -0,0 +1,30 @@
from odoo import models, fields
from odoo.http import request
class ToolInventory(models.Model):
_inherit = 'sf.tool.inventory'
_description = '功能刀具清单'
knife_handle_model = fields.Selection([('BT30', 'BT30'), ('BT40', 'BT40'), ('BT50', 'BT50'), ('GSK30', 'GSK30'), ('GSK40', 'GSK40'), ('GSK50', 'GSK50')], string='使用刀柄型号', required=True)
jikimo_bom_ids = fields.One2many('jikimo.bom','tool_inventory_id', 'bom单')
blade_length = fields.Float('刃长(mm)')
def bom_mainfest(self):
jikimo_bom_ids = self.mapped('jikimo_bom_ids')
if not jikimo_bom_ids:
self._bom_mainfest()
return self.bom_mainfest()
request.session['jikimo_bom_product'] = {'bom_id': int(self.jikimo_bom_ids)}
return {
'type': 'ir.actions.act_window',
'name': '刀具组装清单',
'res_model': 'jikimo.bom',
'view_mode': 'form',
'view_id': self.env.ref('sf_tool_management.view_jikimo_bom_form').id,
'res_id': int(self.jikimo_bom_ids),
'target': 'current', # Use 'new' to open in a new window/tab
}
# 创建bom单
def _bom_mainfest(self):
self.env['jikimo.bom'].create({'tool_inventory_id':self.id})

View File

@@ -38,3 +38,6 @@ access_sf_fixture_material_search_group_plan_dispatch,sf.fixture.material.search
access_sf_functional_tool_dismantle,sf.functional.tool.dismantle,model_sf_functional_tool_dismantle,base.group_user,1,1,1,0
access_sf_functional_tool_dismantle_group_sf_tool_user,sf.functional.tool.dismantle_group_sf_tool_user,model_sf_functional_tool_dismantle,sf_base.group_sf_tool_user,1,1,1,0
access_sf_functional_tool_dismantle_group_plan_dispatch,sf.functional.tool.dismantle_group_plan_dispatch,model_sf_functional_tool_dismantle,sf_base.group_plan_dispatch,1,0,0,0
access_jikimo_bom,jikimo.bom,model_jikimo_bom,base.group_user,1,1,1,1
access_jikimo_bom_wizard,jikimo.bom.wizard,model_jikimo_bom_wizard,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
38
39
40
41
42
43

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.actions.act_window" id="view_jikimo_bom_form_act">
<field name="name">bom物料清单</field>
<field name="res_model">jikimo.bom</field>
<field name="view_mode">tree,form</field>
</record>
<record id="view_jikimo_bom_form" model="ir.ui.view">
<field name="name">jikimo.bom.form</field>
<field name="model">jikimo.bom</field>
<field name="arch" type="xml">
<form create="False">
<header>
<button type="action" name="%(action_jikimo_bom_wizard)d"
class="btn btn-info" string="组装方式.." context="{'default_bom_id':id}"
/>
</header>
<sheet>
<group>
<field name="tool_name"/>
<field name="functional_cutting_tool_model_id"/>
<field name="tool_groups_id"/>
<field name="diameter"/>
</group>
<group>
<field name="angle"/>
<field name="tool_length"/>
<field name="extension"/>
<field name="knife_handle_model"/>
</group>
<notebook colspan="4">
<page string="物料清单">
<field name="product_ids" context="{'jikimo_bom_product': True}">
<tree create="False">
<field name="name"/>
<!-- <field name="categ_id"/>-->
<field name="cutting_tool_material_id"/>
<field name="cutting_tool_type_id"/>
<field name="cutting_tool_model_id"/>
<field name="specification_id"/>
<field name="brand_id"/>
<field name="qty_available"/>
</tree>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>

View File

@@ -11,4 +11,131 @@
</xpath>
</field>
</record>
<record id="view_tool_product_tree" model="ir.ui.view">
<field name="name">刀柄</field>
<field name="model">product.product</field>
<field name="arch" type="xml">
<tree create="0" export_xlsx="0" delete="0">
<header>
<button string="确认" name="set_tool_material" type="object"
class="treeHeaderBtn"/>
</header>
<field name="name"/>
<field name="cutting_tool_type_id"/>
<field name="cutting_tool_model_id"/>
<field name="specification_id"/>
<field name="cutting_tool_shank_length"/>
<field name="cutting_tool_taper_shank_model"/>
<field name="brand_id"/>
<field name="qty_available" string="数量"/>
</tree>
</field>
</record>
<record id="view_tool_product_search" model="ir.ui.view">
<field name="model">product.product</field>
<field name="arch" type="xml">
<search>
<field name="name"/>
</search>
</field>
</record>
<record id="view_shelf_location_lot_tree_1" model="ir.ui.view">
<field name="name">sf.shelf.location.lot.tree</field>
<field name="model">sf.shelf.location.lot</field>
<field name="arch" type="xml">
<tree create="0" export_xlsx="0" delete="0">
<header>
<button string="确认" name="set_tool_material" type="object"
class="treeHeaderBtn"/>
</header>
<field name="product_id"/>
<field name="cutting_tool_type_id"/>
<field name="cutting_tool_model_id"/>
<field name="specification_id"/>
<field name="cutting_tool_blade_diameter"/>
<field name="cutting_tool_blade_tip_working_size"/>
<field name="brand_id"/>
<field name="shelf_location_id"/>
<field name="lot_id"/>
</tree>
</field>
</record>
<record id="view_shelf_location_lot_tree_2" model="ir.ui.view">
<field name="name">sf.shelf.location.lot.tree</field>
<field name="model">sf.shelf.location.lot</field>
<field name="arch" type="xml">
<tree create="0" export_xlsx="0" delete="0">
<header>
<button string="确认" name="set_tool_material" type="object"
class="treeHeaderBtn"/>
</header>
<field name="product_id"/>
<field name="cutting_tool_type_id"/>
<field name="cutting_tool_model_id"/>
<field name="specification_id"/>
<field name="fit_blade_shape_id"/>
<field name="cutting_tool_blade_radius"/>
<field name="brand_id"/>
<field name="shelf_location_id"/>
<field name="lot_id"/>
</tree>
</field>
</record>
<record id="view_shelf_location_lot_tree_3" model="ir.ui.view">
<field name="name">sf.shelf.location.lot.tree</field>
<field name="model">sf.shelf.location.lot</field>
<field name="arch" type="xml">
<tree create="0" export_xlsx="0" delete="0">
<header>
<button string="确认" name="set_tool_material" type="object"
class="treeHeaderBtn"/>
</header>
<field name="product_id"/>
<field name="cutting_tool_type_id"/>
<field name="cutting_tool_model_id"/>
<field name="specification_id"/>
<field name="cutting_tool_cutter_arbor_diameter"/>
<field name="fit_blade_shape_id"/>
<field name="brand_id"/>
<field name="shelf_location_id"/>
<field name="lot_id"/>
</tree>
</field>
</record>
<record id="view_shelf_location_lot_tree_4" model="ir.ui.view">
<field name="name">sf.shelf.location.lot.tree</field>
<field name="model">sf.shelf.location.lot</field>
<field name="arch" type="xml">
<tree create="0" export_xlsx="0" delete="0">
<header>
<button string="确认" name="set_tool_material" type="object"
class="treeHeaderBtn"/>
</header>
<field name="product_id"/>
<field name="cutting_tool_type_id"/>
<field name="cutting_tool_model_id"/>
<field name="specification_id"/>
<field name="cutting_tool_cutter_head_diameter"/>
<field name="fit_blade_shape_id"/>
<field name="brand_id"/>
<field name="shelf_location_id"/>
<field name="lot_id"/>
</tree>
</field>
</record>
</odoo>

View File

@@ -446,19 +446,23 @@
<field name="use_tool_time"/>
<field name="production_line_name_id" optional="hide"/>
<field name="machine_tool_name_id" optional="hide"/>
<field name="applicant"/>
<field name="applicant" optional="hide"/>
<field name="apply_time"/>
<field name="assemble_status" optional="hide"/>
<field name="assemble_status" widget='badge'
decoration-info="assemble_status == '0'"
decoration-warning="assemble_status == '01'"
decoration-success="assemble_status == '1'"
/>
<field name="name" invisible="True"/>
<field name="machine_tool_code" invisible="True"/>
<field name="cutter_spacing_code_id" invisible="True"/>
<field name="whether_standard_knife" invisible="True"/>
<field name="reason_for_applying" invisible="True"/>
<field name="functional_tool_type_id" invisible="True"/>
<button string="组装" name="put_start_preset" type="object"
attrs="{'invisible': [('assemble_status', '!=', '0')]}"
class="btn-primary"/>
<!-- <field name="name" invisible="True"/>-->
<!-- <field name="machine_tool_code" invisible="True"/>-->
<!-- <field name="cutter_spacing_code_id" invisible="True"/>-->
<!-- <field name="whether_standard_knife" invisible="True"/>-->
<!-- <field name="reason_for_applying" invisible="True"/>-->
<!-- <field name="functional_tool_type_id" invisible="True"/>-->
<!-- <button string="组装" name="put_start_preset" type="object"-->
<!-- attrs="{'invisible': [('assemble_status', '!=', '0')]}"-->
<!-- class="btn-primary"/>-->
</tree>
</field>
</record>
@@ -467,19 +471,23 @@
<field name="name">功能刀具组装</field>
<field name="model">sf.functional.tool.assembly</field>
<field name="arch" type="xml">
<form create="0" delete="0" edit="0">
<form create="0" delete="0" edit="1">
<header>
<!-- <button string="修改编码" name="put_assembly_order_code" type="object"-->
<!-- class="btn-primary" confirm="是否确认修改编码"/>-->
<button string="组装" name="put_start_preset" type="object"
attrs="{'invisible': [('assemble_status', '!=', '0')]}"
<button string="开始组装" name="start_preset" type="object"
attrs="{'invisible': [('assemble_status', 'not in', ['0'])]}"
class="btn-primary"/>
<field name="assemble_status" widget="statusbar" statusbar_visible="0,1"/>
<button string="确认组装" name="functional_tool_assembly" type="object"
attrs="{'invisible': [('assemble_status', 'not in', ['01'])]}"
class="btn-primary"/>
<button name="get_tool_preset_parameter" string="获取测量值"
type="object" class="btn-primary"
attrs="{'invisible': [('assemble_status', 'in', ['0','1','2'])]}"
/>
<field name="assemble_status" widget="statusbar" statusbar_visible="0,01,1"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button class="oe_stat_button" name="open_tool_stock_picking" icon="fa-truck" type="object"
attrs="{'invisible': [('assemble_status', '!=', '1')]}">
<button class="oe_stat_button" name="open_tool_stock_picking" icon="fa-truck" type="object">
<div name="delivery_count" class="o_field_widget o_readonly_modifier o_field_statinfo">
<span class="o_stat_info o_stat_value">
<field name="picking_num"/>
@@ -488,6 +496,10 @@
</div>
</button>
</div>
<div class="o_employee_avatar">
<field name="image" widget="image" class="oe_avatar m-0"
attrs="{'readonly': [('assemble_status', '!=', '01')]}"/>
</div>
<div class="oe_title">
<h1>
<field name="assembly_order_code"/>
@@ -496,116 +508,77 @@
<field name="name" invisible="1"/>
<group>
<group>
<field name="production_line_name_id"/>
<field name="machine_tool_name_id"/>
<field name="cutter_spacing_code_id"/>
<field name="sf_machine_table_tool_changing_apply_id"
attrs="{'invisible': [('sf_machine_table_tool_changing_apply_id','=',False)]}"/>
<field name="sf_cam_work_order_program_knife_plan_id"
attrs="{'invisible': [('sf_cam_work_order_program_knife_plan_id','=',False)]}"/>
<field name="functional_tool_name"/>
<field name="functional_tool_type_id"/>
<field name="tool_groups_id"/>
</group>
<group>
<field name="image" nolabel="1" widget="image"/>
<field name="functional_tool_diameter" string="刀具直径(mm)"/>
<field name="knife_tip_r_angle"/>
<field name="tool_loading_length" string="总长度(mm)"/>
<field name="functional_tool_length"/>
</group>
</group>
<notebook>
<page string="组装信息" attrs="{'invisible': [('assemble_status', '=', '0')]}">
<group col="1">
<page string="组装信息">
<field name="_barcode_scanned" widget="barcode_handler"/>
<group attrs="{'invisible': [('assemble_status', '=', '0')]}">
<group col="1">
<group>
<group col="1">
<div>
<separator string="刀柄:" style="font-size: 13px;"/>
</div>
<group>
<field name="barcode_id" invisible="True"/>
<field name="rfid" string="功能刀具rfid"/>
<field name="code"/>
<field name="after_assembly_functional_tool_name"
string="功能刀具名称"/>
<field name="after_assembly_functional_tool_type_id"
string="功能刀具类型"/>
<field name="tool_groups_id"/>
<field name="after_assembly_whether_standard_knife"
string="是否标准刀"/>
<field name="after_assembly_coarse_middle_thin" string="粗/中/精"/>
<field name="after_assembly_new_former" string="新/旧"/>
<field name="cut_time"
attrs="{'invisible': [('after_assembly_new_former', '=', '0')]}"/>
<field name="cut_length"
attrs="{'invisible': [('after_assembly_new_former', '=', '0')]}"/>
<field name="cut_number"
attrs="{'invisible': [('after_assembly_new_former', '=', '0')]}"/>
</group>
<group>
<field name="after_assembly_functional_tool_diameter"
string="刀具直径(mm)"/>
<field name="after_assembly_knife_tip_r_angle" string="刀尖R角(mm)"/>
<field name="after_assembly_tool_loading_length" string="总长度(mm)"/>
<field name="after_assembly_handle_length" string="刀柄长度(mm)"/>
<field name="after_assembly_functional_tool_length"
string="伸出长(mm)"/>
<field name="after_assembly_max_lifetime_value"
string="最大寿命值(min)"/>
<field name="after_assembly_alarm_value" string="报警值(min)"/>
<field name="after_assembly_used_value" string="已使用值(min)"
invisible="1"/>
<field name="after_assembly_effective_length" string="有效长(mm)"
invisible="1"/>
<field name="L_D_number" invisible="1"/>
<field name="hiding_length" invisible="1"/>
<field name="handle_code_id" string="序列号" placeholder="请选择"
options="{'no_create': True, 'no_quick_create': True}"/>
<field name="handle_freight_rfid" string="Rfid"/>
<field name="handle_product_id" string="名称"/>
<field name="cutting_tool_cutterhandle_model_id" string="型号"/>
<field name="handle_specification_id" string="规格"/>
<field name="sf_tool_brand_id_5" string="品牌"/>
</group>
<div>
<button string="更多" name="set_tool_lot" type="object"
class="btn-primary" context="{'tool_type': '刀柄'}"
attrs="{'invisible': [('assemble_status', 'not in', ['0','01'])]}"/>
</div>
</group>
</group>
</group>
<group>
<group col="1" attrs="{'invisible': [('handle_code_id', '=', False)]}">
<div>
<separator string="刀柄:" style="font-size: 13px;"/>
</div>
<group>
<field name="handle_code_id" string="序列号" placeholder="请选择"
options="{'no_create': True, 'no_quick_create': True}"/>
<field name="handle_freight_rfid" string="Rfid"/>
<field name="handle_product_id" string="名称"/>
<field name="cutting_tool_cutterhandle_model_id" string="型号"/>
<field name="handle_specification_id" string="规格"/>
<field name="sf_tool_brand_id_5" string="品牌"/>
</group>
</group>
<group col="1" attrs="{'invisible': [('chuck_freight_barcode_id', '=', False)]}">
<div>
<separator string="夹头:" style="font-size: 13px;"/>
</div>
<group>
<group>
<field name="chuck_freight_barcode_id" string="货位"/>
<field name="chuck_lot_id" string="批次"/>
<field name="chuck_product_id" string="名称"/>
<field name="cutting_tool_cutterhead_model_id" string="型号"/>
<field name="chuck_specification_id" string="规格"/>
<field name="sf_tool_brand_id_6" string="品牌"/>
</group>
</group>
</group>
</group>
<group>
<group col="1">
<group col="1"
attrs="{'invisible': [('integral_freight_barcode_id', '=', False)]}">
attrs="{'invisible': [('integral_lot_id', '=', False),'|','|','|', ('blade_lot_id', '!=', False),('bar_lot_id', '!=', False),('pad_lot_id', '!=', False),('assemble_status', 'not in', ['0','01'])]}">
<div>
<separator string="整体式刀具:" style="font-size: 13px;"/>
</div>
<group>
<field name="integral_freight_barcode_id" string="货位"/>
<field name="integral_lot_id" string="批次"/>
<field name="integral_product_id" string="名称"/>
<field name="cutting_tool_integral_model_id" string="型号"/>
<field name="integral_specification_id" string="规格"/>
<field name="sf_tool_brand_id_1" string="品牌"/>
<group>
<field name="integral_freight_barcode_id" string="货位"/>
<field name="integral_lot_id" string="批次"/>
<field name="integral_product_id" string="名称"/>
<field name="cutting_tool_integral_model_id" string="型号"/>
<field name="integral_specification_id" string="规格"/>
<field name="sf_tool_brand_id_1" string="品牌"/>
</group>
<group>
<field name="integral_verify" string="" readonly="1"/>
</group>
</group>
</group>
<group col="1"
attrs="{'invisible': [('blade_freight_barcode_id', '=', False)]}">
<div>
<separator string="刀片:" style="font-size: 13px;"/>
<button string="更多" name="set_tool_lot" type="object"
class="btn-primary" context="{'tool_type': '整体式刀具'}"
attrs="{'invisible': [('assemble_status', 'not in', ['0','01'])]}"/>
</div>
</group>
</group>
</group>
<group attrs="{'invisible': [('assemble_status', '=', '0')]}">
<group col="1"
attrs="{'invisible': [('blade_lot_id', '=', False),'|', ('integral_lot_id', '!=', False),('assemble_status', 'not in', ['0','01'])]}">
<div>
<separator string="刀片:" style="font-size: 13px;"/>
</div>
<group>
<group>
<field name="blade_freight_barcode_id" string="货位"/>
<field name="blade_lot_id" string="批次"/>
@@ -614,10 +587,19 @@
<field name="blade_specification_id" string="规格"/>
<field name="sf_tool_brand_id_2" string="品牌"/>
</group>
<group>
<field name="blade_verify" string="" readonly="1"/>
</group>
</group>
<div>
<button string="更多" name="set_tool_lot" type="object"
class="btn-primary" context="{'tool_type': '刀片'}"
attrs="{'invisible': [('assemble_status', 'not in', ['0','01'])]}"/>
</div>
</group>
<group col="1">
<group col="1" attrs="{'invisible': [('bar_freight_barcode_id', '=', False)]}">
<group col="1"
attrs="{'invisible': [('bar_lot_id', '=', False),'|','|',('integral_lot_id', '!=', False),('pad_lot_id', '!=', False),('assemble_status', 'not in', ['0','01'])]}">
<div>
<separator string="刀杆:" style="font-size: 13px;"/>
</div>
@@ -630,9 +612,18 @@
<field name="bar_specification_id" string="规格"/>
<field name="sf_tool_brand_id_3" string="品牌"/>
</group>
<group>
<field name="bar_verify" string="" readonly="1"/>
</group>
</group>
<div>
<button string="更多" name="set_tool_lot" type="object"
class="btn-primary" context="{'tool_type': '刀杆'}"
attrs="{'invisible': [('assemble_status', 'not in', ['0','01'])]}"/>
</div>
</group>
<group col="1" attrs="{'invisible': [('pad_freight_barcode_id', '=', False)]}">
<group col="1"
attrs="{'invisible': [('pad_lot_id', '=', False),'|','|',('integral_lot_id', '!=', False),('bar_lot_id', '!=', False),('assemble_status', 'not in', ['0','01'])]}">
<div>
<separator string="刀盘:" style="font-size: 13px;"/>
</div>
@@ -645,30 +636,69 @@
<field name="pad_specification_id" string="规格"/>
<field name="sf_tool_brand_id_4" string="品牌"/>
</group>
<group>
<field name="pad_verify" string="" readonly="1"/>
</group>
</group>
<div>
<button string="更多" name="set_tool_lot" type="object"
class="btn-primary" context="{'tool_type': '刀盘'}"
attrs="{'invisible': [('assemble_status', 'not in', ['0','01'])]}"/>
</div>
</group>
</group>
</group>
</page>
<page string="申请信息">
<group>
<group>
<field name="functional_tool_name"/>
<field name="functional_tool_type_id"/>
<field name="tool_groups_id"/>
<field name="functional_tool_diameter" string="刀具直径(mm)"/>
<field name="knife_tip_r_angle"/>
<field name="tool_loading_length" string="总长度(mm)"/>
<field name="functional_tool_length"/>
<field name="effective_length"/>
</group>
<group>
<field name="whether_standard_knife"/>
<field name="coarse_middle_thin"/>
<field name="new_former"/>
<field name="use_tool_time"/>
<field name="reason_for_applying"/>
<!-- <field name="functional_tool_cutting_type"/>-->
<group col="1" attrs="{'invisible': [('assemble_status', '=', '0')]}">
<group col="1">
<group string="组装参数信息">
<group>
<field name="barcode_id" invisible="True"/>
<field name="rfid" string="功能刀具rfid"/>
<field name="code"/>
<field name="after_assembly_functional_tool_name"
string="功能刀具名称"/>
<field name="after_assembly_functional_tool_type_id"
string="功能刀具类型"/>
<field name="tool_groups_id"/>
<field name="after_assembly_whether_standard_knife"
string="是否标准刀" invisible="1"/>
<field name="after_assembly_coarse_middle_thin" string="粗/中/精"
attrs="{'readonly': [('assemble_status', 'in', ['1','2'])]}"/>
<field name="after_assembly_new_former" string="新/旧"
attrs="{'readonly': [('assemble_status', 'in', ['1','2'])]}"/>
<field name="cut_time"
attrs="{'invisible': [('after_assembly_new_former', '=', '0')]}"/>
<field name="cut_length"
attrs="{'invisible': [('after_assembly_new_former', '=', '0')]}"/>
<field name="cut_number"
attrs="{'invisible': [('after_assembly_new_former', '=', '0')]}"/>
</group>
<group>
<field name="after_assembly_functional_tool_diameter" readonly="0"
string="刀具直径(mm)"
attrs="{'readonly': [('assemble_status', 'in', ['1','2'])]}"/>
<field name="after_assembly_knife_tip_r_angle" readonly="0"
string="刀尖R角(mm)"
attrs="{'readonly': [('assemble_status', 'in', ['1','2'])]}"/>
<field name="after_assembly_tool_loading_length" readonly="0"
string="总长度(mm)"
attrs="{'readonly': [('assemble_status', 'in', ['1','2'])]}"/>
<field name="after_assembly_handle_length" string="刀柄长度(mm)"
attrs="{'readonly': [('assemble_status', 'in', ['1','2'])]}"/>
<field name="after_assembly_functional_tool_length"
string="伸出长(mm)"/>
<field name="after_assembly_max_lifetime_value"
string="最大寿命值(min)"/>
<field name="after_assembly_alarm_value" string="报警值(min)"
invisible="1"/>
<field name="after_assembly_used_value" string="已使用值(min)"
invisible="1"/>
<field name="after_assembly_effective_length" string="有效长(mm)"
invisible="1"/>
<field name="L_D_number" invisible="1"/>
<field name="hiding_length" invisible="1"/>
</group>
</group>
</group>
</group>
</page>
@@ -685,9 +715,27 @@
<page string="其他">
<group>
<group>
<field name="production_line_name_id"
attrs="{'invisible': [('production_line_name_id','=',False)]}"/>
<field name="machine_tool_name_id"
attrs="{'invisible': [('machine_tool_name_id','=',False)]}"/>
<field name="cutter_spacing_code_id"
attrs="{'invisible': [('cutter_spacing_code_id','=',False)]}"/>
<field name="use_tool_time"/>
<field name="reason_for_applying"/>
<field name="sf_machine_table_tool_changing_apply_id"
attrs="{'invisible': [('sf_machine_table_tool_changing_apply_id','=',False)]}"/>
<field name="sf_cam_work_order_program_knife_plan_id"
attrs="{'invisible': [('sf_cam_work_order_program_knife_plan_id','=',False)]}"/>
<field name="check_box_1" invisible="True"/>
<field name="remark"/>
</group>
<group>
<field name="effective_length"/>
<field name="whether_standard_knife"/>
<field name="coarse_middle_thin" invisible="1"/>
<field name="new_former" invisible="1"/>
</group>
</group>
</page>
</notebook>
@@ -705,20 +753,19 @@
<field name="arch" type="xml">
<search>
<field name="functional_tool_name"/>
<field name="tool_groups_id"/>
<field name="functional_tool_diameter"/>
<field name="knife_tip_r_angle"/>
<field name="assembly_order_code"/>
<field name="code" string="功能刀具编码"/>
<field name="barcode_id"/>
<field name="functional_tool_type_id"/>
<field name="tool_groups_id"/>
<field name="loading_task_source" string="任务来源"/>
<field name="production_line_name_id"/>
<field name="machine_tool_name_id"/>
<field name="applicant"/>
<filter name="no_assemble_status" string="未组装" domain="[('assemble_status', '=', '0')]"/>
<filter name="no_assemble_status" string="未组装" domain="[('assemble_status', 'in', ['0','01'])]"/>
<filter name="yes_assemble_status" string="已组装" domain="[('assemble_status', '=', '1')]"/>
<separator/>
<filter string="已归档" name="inactive" domain="[('active', '=', False)]"/>
<searchpanel>
<field name="assemble_status" enable_counters="1" icon="fa-filter"/>
<field name="functional_tool_type_id" enable_counters="1" icon="fa-filter"/>
</searchpanel>
@@ -743,7 +790,7 @@
<field name="view_mode">tree,form,search</field>
<!-- <field name="view_id" ref="sf_functional_tool_assembly_tree"/>-->
<field name="search_view_id" ref="sf_functional_tool_assembly_search"/>
<field name="context">{'search_default_no_assemble_status':1}</field>
<field name="context">{'search_default_no_assemble_status':[1,01]}</field>
</record>
@@ -844,7 +891,8 @@
<group attrs="{'invisible': [('dismantle_cause', 'not in', ['寿命到期报废','崩刀报废'])]}">
<!-- <group col="3">-->
<group>
<field name="scrap_boolean" string="是否报废" readonly="0"/>
<field name="scrap_boolean" string="是否报废"
attrs="{'readonly': [('state' , '=', '已拆解')]}"/>
</group>
<!-- <group></group>-->
<!-- <group>-->

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_tool_inventory_inherit_tree" model="ir.ui.view">
<field name="name">sf.tool.inventory.inherit.tree</field>
<field name="model">sf.tool.inventory</field>
<field name="inherit_id" ref="sf_base.view_tool_inventory_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='extension']" position="before">
<field name="knife_handle_model" class="o-sticky-header"/>
<button name="bom_mainfest" string="bom清单" type="object" class="btn-link"
icon="fa-refresh"/>
</xpath>
</field>
</record>
<record id="view_tool_inventory_inherit_search" model="ir.ui.view">
<field name="name">sf.tool.inventory.inherit.search</field>
<field name="model">sf.tool.inventory</field>
<field name="inherit_id" ref="sf_base.view_cutting_tool_material_search"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='extension']" position="after">
<searchpanel>
<field name="functional_cutting_tool_model_id" enable_counters="1"/>
<!-- <field name="job_id" enable_counters="1"/>-->
<!-- <field name="department_id" enable_counters="1"/>-->
<!-- <field name="company_id" enable_counters="1"/>-->
</searchpanel>
</xpath>
</field>
</record>
<!-- <searchpanel>-->
<!-- <field name="org_type_id_display" enable_counters="1"/>-->
<!-- &lt;!&ndash; <field name="job_id" enable_counters="1"/>&ndash;&gt;-->
<!-- <field name="department_id" enable_counters="1"/>-->
<!-- &lt;!&ndash; <field name="company_id" enable_counters="1"/>&ndash;&gt;-->
<!-- </searchpanel>-->
</odoo>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<odoo>
<!-- ================================================功能刀具类型================================================ -->
<record id="view_cutter_function_inherit_tree" model="ir.ui.view">
<field name="name">sf.cutter.function.inherit.tree</field>
<field name="model">sf.functional.cutting.tool.model</field>
<field name="inherit_id" ref="sf_base.view_cutter_function_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='name']" position="after">
<field name="cutting_tool_type_ids" widget="many2many_tags" options="{'no_create': True}"/>
</xpath>
</field>
</record>
<!-- 功能刀具类型搜索 -->
</odoo>

View File

@@ -1 +1,2 @@
from . import wizard
from . import jikimo_bom_wizard

View File

@@ -0,0 +1,43 @@
import logging
from datetime import timedelta, datetime, date
from odoo import fields, models, api
from odoo.exceptions import ValidationError, UserError
class JikimoBomWizard(models.TransientModel):
_name = 'jikimo.bom.wizard'
_description = '组装方式选择'
bom_id = fields.Many2one('jikimo.bom', '刀具组装清单')
assembly_options = fields.Selection([
('刀柄+整体式刀具', '刀柄+整体式刀具'),
('刀柄+刀杆+刀片', '刀柄+刀杆+刀片'),
('刀柄+刀盘+刀片', '刀柄+刀盘+刀片')
], string='组装方式', required=True)
is_ok = fields.Boolean('确认上述信息正确无误。')
@api.model
def default_get(self, fields):
res = super(JikimoBomWizard, self).default_get(fields)
# 根据某个字段的值设置默认选项
if 'default_bom_id' in self.env.context:
jikimo_bom = self.env['jikimo.bom'].browse(self.env.context['default_bom_id'])
if not jikimo_bom:
return res
if jikimo_bom.options:
res['assembly_options'] = jikimo_bom.options
# some_field_value = self.env.context.get('some_field')
# if some_field_value == 'condition_value':
# res['default_option'] = 'option2' # 设置为特定选项
return res
def submit(self):
if not self.bom_id:
raise UserError('缺少bom信息')
if self.bom_id.tool_inventory_id.functional_cutting_tool_model_id.name == '飞刀' and self.assembly_options == '刀柄+整体式刀具':
raise UserError('飞刀只可选 刀柄+刀杆+刀片 或 刀柄+刀盘+刀片')
if self.bom_id.tool_inventory_id.functional_cutting_tool_model_id.name in['中心钻','合金钻','合金刀','整体刀','倒角刀','丝锥'] and self.assembly_options != '刀柄+整体式刀具':
raise UserError('此功能刀具只可选 刀柄+整体式刀具')
self.bom_id.generate_bill_materials(self.assembly_options)

View File

@@ -0,0 +1,33 @@
<openerp>
<data>
<record id="action_jikimo_bom_wizard" model="ir.actions.act_window">
<field name="name">组装方式..</field>
<field name="res_model">jikimo.bom.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
</record>
<record model="ir.ui.view" id="jikimo_bom_wizard_form_view">
<field name="name">jikimo.bom.wizard.form.view</field>
<field name="model">jikimo.bom.wizard</field>
<field name="arch" type="xml">
<form string="组装方式..">
<group>
<field name="assembly_options"/>
<!-- <field name="factory_no" required="1"/>-->
</group>
<div>
<field name="is_ok"/>
确认上述信息正确无误.
</div>
<footer>
<button string="确认组装方式" name="submit" type="object" class="oe_highlight"
attrs="{'invisible':[('is_ok','=',False)]}"/>
<button string="取消" class="btn btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</data>
</openerp>

View File

@@ -598,361 +598,175 @@ class FunctionalToolAssemblyOrder(models.TransientModel):
else:
record.L_D_number = 0
def functional_tool_assembly(self):
"""
功能刀具组装
:return:
"""
logging.info('功能刀具开始组装!')
# 获取组装单对象
functional_tool_assembly = self.env['sf.functional.tool.assembly'].search([
('assembly_order_code', '=', self.assembly_order_code),
('machine_tool_name_id', '=', self.machine_tool_name_id.id),
('cutter_spacing_code_id', '=', self.cutter_spacing_code_id.id),
('assemble_status', '=', '0'),
])
# 对物料做必填判断
self.materials_must_be_judged()
product_id = self.env['product.product']
# 创建组装入库单
# 创建功能刀具批次/序列号记录
stock_lot = product_id.create_assemble_warehouse_receipt(self.id, functional_tool_assembly, self)
# 封装功能刀具数据,用于更新组装单信息
desc_1 = self.get_desc_1(stock_lot)
# 封装功能刀具数据,用于创建功能刀具记录
desc_2 = self.get_desc_2(stock_lot, functional_tool_assembly)
# 创建功能刀具组装入库单
self.env['stock.picking'].create_tool_stocking_picking(stock_lot, functional_tool_assembly, self)
# 创建刀具物料出库单
self.env['stock.picking'].create_tool_stocking_picking1(self)
# ============================创建功能刀具列表、安全库存记录===============================
# 创建功能刀具列表记录
record_1 = self.env['sf.functional.cutting.tool.entity'].create(desc_2)
# 创建安全库存信息
self.env['sf.real.time.distribution.of.functional.tools'].create_or_edit_safety_stock({
'functional_name_id': self.after_name_id.id
}, record_1)
# =====================修改功能刀具组装单、机床换刀申请、CAM工单程序用刀计划的状态==============
# 修改功能刀具组装单信息
functional_tool_assembly.write(desc_1)
if functional_tool_assembly.sf_machine_table_tool_changing_apply_id:
# 修改机床换刀申请的状态
self.env['sf.machine.table.tool.changing.apply'].sudo().search([
('id', '=', functional_tool_assembly.sf_machine_table_tool_changing_apply_id.id)
]).write({'status': '3'})
elif functional_tool_assembly.sf_cam_work_order_program_knife_plan_id:
# 修改CAM工单程序用刀计划状态
cam_plan = self.env['sf.cam.work.order.program.knife.plan'].sudo().search([
('id', '=', functional_tool_assembly.sf_cam_work_order_program_knife_plan_id.id)
])
cam_plan.write({'plan_execute_status': '2'})
logging.info('功能刀具组装完成!')
# 关闭弹出窗口
return {'type': 'ir.actions.act_window_close'}
def materials_must_be_judged(self):
"""
功能刀具组装必填判断
"""
# 物料必填校验
if not self.handle_code_id:
raise ValidationError('缺少【刀柄】物料信息!')
if not self.integral_product_id and not self.blade_product_id:
raise ValidationError('【整体式刀具】和【刀片】必须填写一个!')
if self.blade_product_id:
if not self.bar_product_id and not self.pad_product_id:
raise ValidationError('【刀盘】和【刀杆】必须填写一个!')
# 组装参数必填校验
if self.after_assembly_functional_tool_length == 0:
raise ValidationError('组装参数信息【伸出长】不能为0')
if self.after_assembly_max_lifetime_value == 0:
raise ValidationError('组装参数信息【最大寿命值】不能为0')
if self.after_assembly_alarm_value == 0:
raise ValidationError('组装参数信息【报警值】不能为0')
# if self.after_assembly_effective_length == 0:
# raise ValidationError('组装参数信息【有效长】不能为0')
# if self.hiding_length == 0:
# raise ValidationError('组装参数信息【避空长】不能为0')
if self.after_assembly_functional_tool_diameter == 0:
raise ValidationError('组装参数信息【刀具直径】不能为0')
if self.after_assembly_tool_loading_length == 0:
raise ValidationError('组装参数信息【总长度】不能为0')
if self.after_assembly_handle_length == 0:
raise ValidationError('组装参数信息【刀柄长度】不能为0')
if self.after_assembly_tool_loading_length < self.after_assembly_handle_length:
raise ValidationError('组装参数信息【刀柄长度】不能大于【总长度】!')
def get_desc_1(self, stock_lot):
return {
'start_preset_bool': False,
'barcode_id': stock_lot.id,
'code': self.code,
'rfid': self.rfid,
'tool_groups_id': self.after_tool_groups_id.id,
'handle_code_id': self.handle_code_id.id,
'integral_freight_barcode_id': self.integral_freight_barcode_id.id,
'integral_lot_id': self.integral_freight_lot_id.lot_id.id,
'blade_freight_barcode_id': self.blade_freight_barcode_id.id,
'blade_lot_id': self.blade_freight_lot_id.lot_id.id,
'bar_freight_barcode_id': self.bar_freight_barcode_id.id,
'bar_lot_id': self.bar_freight_lot_id.lot_id.id,
'pad_freight_barcode_id': self.pad_freight_barcode_id.id,
'pad_lot_id': self.pad_freight_lot_id.lot_id.id,
'chuck_freight_barcode_id': self.chuck_freight_barcode_id.id,
'chuck_lot_id': self.chuck_freight_lot_id.lot_id.id,
'after_assembly_functional_tool_name': self.after_assembly_functional_tool_name,
'after_assembly_functional_tool_type_id': self.after_assembly_functional_tool_type_id.id,
'after_assembly_functional_tool_diameter': self.after_assembly_functional_tool_diameter,
'after_assembly_knife_tip_r_angle': self.after_assembly_knife_tip_r_angle,
'after_assembly_new_former': self.after_assembly_new_former,
'cut_time': self.cut_time,
'cut_length': self.cut_length,
'cut_number': self.cut_number,
'after_assembly_whether_standard_knife': self.after_assembly_whether_standard_knife,
'after_assembly_coarse_middle_thin': self.after_assembly_coarse_middle_thin,
'after_assembly_max_lifetime_value': self.after_assembly_max_lifetime_value,
'after_assembly_alarm_value': self.after_assembly_alarm_value,
'after_assembly_used_value': self.after_assembly_used_value,
'after_assembly_tool_loading_length': self.after_assembly_tool_loading_length,
'after_assembly_handle_length': self.after_assembly_handle_length,
'after_assembly_functional_tool_length': self.after_assembly_functional_tool_length,
'after_assembly_effective_length': self.after_assembly_effective_length,
'L_D_number': self.L_D_number,
'hiding_length': self.hiding_length,
'assemble_status': '1',
'tool_loading_person': self.env.user.name,
'image': self.image,
'tool_loading_time': fields.Datetime.now()
}
def get_desc_2(self, stock_lot, functional_tool_assembly_id):
return {
'barcode_id': stock_lot.id,
'code': self.code,
'name': self.after_name_id.name,
'tool_name_id': self.after_name_id.id,
'rfid': self.rfid,
'tool_groups_id': self.after_tool_groups_id.id,
'functional_tool_name_id': functional_tool_assembly_id.id,
'sf_cutting_tool_type_id': self.after_assembly_functional_tool_type_id.id,
'cutting_tool_integral_model_id': self.integral_product_id.id,
'cutting_tool_blade_model_id': self.blade_product_id.id,
'cutting_tool_cutterbar_model_id': self.bar_product_id.id,
'cutting_tool_cutterpad_model_id': self.pad_product_id.id,
'cutting_tool_cutterhandle_model_id': self.handle_product_id.id,
'cutting_tool_cutterhead_model_id': self.chuck_product_id.id,
'functional_tool_diameter': self.after_assembly_functional_tool_diameter,
'knife_tip_r_angle': self.after_assembly_knife_tip_r_angle,
'coarse_middle_thin': self.after_assembly_coarse_middle_thin,
'new_former': self.after_assembly_new_former,
'tool_loading_length': self.after_assembly_tool_loading_length,
'handle_length': self.after_assembly_handle_length,
'functional_tool_length': self.after_assembly_functional_tool_length,
'effective_length': self.after_assembly_effective_length,
'max_lifetime_value': self.after_assembly_max_lifetime_value,
'alarm_value': self.after_assembly_alarm_value,
'used_value': self.after_assembly_used_value,
'whether_standard_knife': self.after_assembly_whether_standard_knife,
'L_D_number': self.L_D_number,
'hiding_length': self.hiding_length,
'cut_time': self.cut_time,
'cut_length': self.cut_length,
'cut_number': self.cut_number,
'image': self.image,
}
# def functional_tool_assembly(self):
# """
# 功能刀具组装
# :return:
# """
# logging.info('功能刀具开始组装!')
# # 获取组装单对象
# functional_tool_assembly = self.env['sf.functional.tool.assembly'].search([
# ('assembly_order_code', '=', self.assembly_order_code),
# ('machine_tool_name_id', '=', self.machine_tool_name_id.id),
# ('cutter_spacing_code_id', '=', self.cutter_spacing_code_id.id),
# ('assemble_status', '=', '0'),
# ])
# # 对物料做必填判断
# self.materials_must_be_judged()
#
# product_id = self.env['product.product']
# # 创建组装入库单
# # 创建功能刀具批次/序列号记录
# stock_lot = product_id.create_assemble_warehouse_receipt(self.id, functional_tool_assembly, self)
# # 封装功能刀具数据,用于更新组装单信息
# desc_1 = self.get_desc_1(stock_lot)
# # 封装功能刀具数据,用于创建功能刀具记录
# desc_2 = self.get_desc_2(stock_lot, functional_tool_assembly)
# # 创建功能刀具组装入库单
# self.env['stock.picking'].create_tool_stocking_picking(stock_lot, functional_tool_assembly, self)
# # 创建刀具物料出库单
# self.env['stock.picking'].create_tool_stocking_picking1(self)
#
# # ============================创建功能刀具列表、安全库存记录===============================
# # 创建功能刀具列表记录
# record_1 = self.env['sf.functional.cutting.tool.entity'].create(desc_2)
# # 创建安全库存信息
# self.env['sf.real.time.distribution.of.functional.tools'].create_or_edit_safety_stock({
# 'functional_name_id': self.after_name_id.id
# }, record_1)
#
# # =====================修改功能刀具组装单、机床换刀申请、CAM工单程序用刀计划的状态==============
# # 修改功能刀具组装单信息
# functional_tool_assembly.write(desc_1)
# if functional_tool_assembly.sf_machine_table_tool_changing_apply_id:
# # 修改机床换刀申请的状态
# self.env['sf.machine.table.tool.changing.apply'].sudo().search([
# ('id', '=', functional_tool_assembly.sf_machine_table_tool_changing_apply_id.id)
# ]).write({'status': '3'})
# elif functional_tool_assembly.sf_cam_work_order_program_knife_plan_id:
# # 修改CAM工单程序用刀计划状态
# cam_plan = self.env['sf.cam.work.order.program.knife.plan'].sudo().search([
# ('id', '=', functional_tool_assembly.sf_cam_work_order_program_knife_plan_id.id)
# ])
# cam_plan.write({'plan_execute_status': '2'})
#
# logging.info('功能刀具组装完成!')
#
# # 关闭弹出窗口
# return {'type': 'ir.actions.act_window_close'}
#
# def materials_must_be_judged(self):
# """
# 功能刀具组装必填判断
# """
# # 物料必填校验
# if not self.handle_code_id:
# raise ValidationError('缺少【刀柄】物料信息!')
# if not self.integral_product_id and not self.blade_product_id:
# raise ValidationError('【整体式刀具】和【刀片】必须填写一个!')
# if self.blade_product_id:
# if not self.bar_product_id and not self.pad_product_id:
# raise ValidationError('【刀盘】和【刀杆】必须填写一个!')
# # 组装参数必填校验
# if self.after_assembly_functional_tool_length == 0:
# raise ValidationError('组装参数信息【伸出长】不能为0')
# if self.after_assembly_max_lifetime_value == 0:
# raise ValidationError('组装参数信息【最大寿命值】不能为0')
# if self.after_assembly_alarm_value == 0:
# raise ValidationError('组装参数信息【报警值】不能为0')
# # if self.after_assembly_effective_length == 0:
# # raise ValidationError('组装参数信息【有效长】不能为0')
# # if self.hiding_length == 0:
# # raise ValidationError('组装参数信息【避空长】不能为0')
# if self.after_assembly_functional_tool_diameter == 0:
# raise ValidationError('组装参数信息【刀具直径】不能为0')
# if self.after_assembly_tool_loading_length == 0:
# raise ValidationError('组装参数信息【总长度】不能为0')
# if self.after_assembly_handle_length == 0:
# raise ValidationError('组装参数信息【刀柄长度】不能为0')
# if self.after_assembly_tool_loading_length < self.after_assembly_handle_length:
# raise ValidationError('组装参数信息【刀柄长度】不能大于【总长度】!')
#
# def get_desc_1(self, stock_lot):
# return {
# 'start_preset_bool': False,
# 'barcode_id': stock_lot.id,
# 'code': self.code,
# 'rfid': self.rfid,
# 'tool_groups_id': self.after_tool_groups_id.id,
# 'handle_code_id': self.handle_code_id.id,
# 'integral_freight_barcode_id': self.integral_freight_barcode_id.id,
# 'integral_lot_id': self.integral_freight_lot_id.lot_id.id,
# 'blade_freight_barcode_id': self.blade_freight_barcode_id.id,
# 'blade_lot_id': self.blade_freight_lot_id.lot_id.id,
# 'bar_freight_barcode_id': self.bar_freight_barcode_id.id,
# 'bar_lot_id': self.bar_freight_lot_id.lot_id.id,
# 'pad_freight_barcode_id': self.pad_freight_barcode_id.id,
# 'pad_lot_id': self.pad_freight_lot_id.lot_id.id,
# 'chuck_freight_barcode_id': self.chuck_freight_barcode_id.id,
# 'chuck_lot_id': self.chuck_freight_lot_id.lot_id.id,
#
# 'after_assembly_functional_tool_name': self.after_assembly_functional_tool_name,
# 'after_assembly_functional_tool_type_id': self.after_assembly_functional_tool_type_id.id,
# 'after_assembly_functional_tool_diameter': self.after_assembly_functional_tool_diameter,
# 'after_assembly_knife_tip_r_angle': self.after_assembly_knife_tip_r_angle,
# 'after_assembly_new_former': self.after_assembly_new_former,
# 'cut_time': self.cut_time,
# 'cut_length': self.cut_length,
# 'cut_number': self.cut_number,
# 'after_assembly_whether_standard_knife': self.after_assembly_whether_standard_knife,
# 'after_assembly_coarse_middle_thin': self.after_assembly_coarse_middle_thin,
# 'after_assembly_max_lifetime_value': self.after_assembly_max_lifetime_value,
# 'after_assembly_alarm_value': self.after_assembly_alarm_value,
# 'after_assembly_used_value': self.after_assembly_used_value,
# 'after_assembly_tool_loading_length': self.after_assembly_tool_loading_length,
# 'after_assembly_handle_length': self.after_assembly_handle_length,
# 'after_assembly_functional_tool_length': self.after_assembly_functional_tool_length,
# 'after_assembly_effective_length': self.after_assembly_effective_length,
# 'L_D_number': self.L_D_number,
# 'hiding_length': self.hiding_length,
# 'assemble_status': '1',
# 'tool_loading_person': self.env.user.name,
# 'image': self.image,
# 'tool_loading_time': fields.Datetime.now()
# }
#
# def get_desc_2(self, stock_lot, functional_tool_assembly_id):
# return {
# 'barcode_id': stock_lot.id,
# 'code': self.code,
# 'name': self.after_name_id.name,
# 'tool_name_id': self.after_name_id.id,
# 'rfid': self.rfid,
# 'tool_groups_id': self.after_tool_groups_id.id,
# 'functional_tool_name_id': functional_tool_assembly_id.id,
# 'sf_cutting_tool_type_id': self.after_assembly_functional_tool_type_id.id,
# 'cutting_tool_integral_model_id': self.integral_product_id.id,
# 'cutting_tool_blade_model_id': self.blade_product_id.id,
# 'cutting_tool_cutterbar_model_id': self.bar_product_id.id,
# 'cutting_tool_cutterpad_model_id': self.pad_product_id.id,
# 'cutting_tool_cutterhandle_model_id': self.handle_product_id.id,
# 'cutting_tool_cutterhead_model_id': self.chuck_product_id.id,
#
# 'functional_tool_diameter': self.after_assembly_functional_tool_diameter,
# 'knife_tip_r_angle': self.after_assembly_knife_tip_r_angle,
# 'coarse_middle_thin': self.after_assembly_coarse_middle_thin,
# 'new_former': self.after_assembly_new_former,
# 'tool_loading_length': self.after_assembly_tool_loading_length,
# 'handle_length': self.after_assembly_handle_length,
# 'functional_tool_length': self.after_assembly_functional_tool_length,
# 'effective_length': self.after_assembly_effective_length,
#
# 'max_lifetime_value': self.after_assembly_max_lifetime_value,
# 'alarm_value': self.after_assembly_alarm_value,
# 'used_value': self.after_assembly_used_value,
# 'whether_standard_knife': self.after_assembly_whether_standard_knife,
# 'L_D_number': self.L_D_number,
# 'hiding_length': self.hiding_length,
# 'cut_time': self.cut_time,
# 'cut_length': self.cut_length,
# 'cut_number': self.cut_number,
# 'image': self.image,
# }
class StockPicking(models.Model):
_inherit = 'stock.picking'
def create_tool_stocking_picking(self, stock_lot, functional_tool_assembly, obj):
"""
创建功能刀具组装入库单
"""
# 获取名称为刀具组装入库的作业类型
picking_type_id = self.env['stock.picking.type'].sudo().search([('name', '=', '刀具组装入库')])
# 创建刀具组装入库单
picking_id = self.env['stock.picking'].create({
'name': self._get_name_stock(picking_type_id),
'picking_type_id': picking_type_id.id,
'location_id': picking_type_id.default_location_src_id.id,
'location_dest_id': picking_type_id.default_location_dest_id.id,
'origin': obj.assembly_order_code
})
# 创建作业详情对象记录,并绑定到刀具组装入库单
self.env['stock.move.line'].create({
'picking_id': picking_id.id,
'product_id': stock_lot.product_id.id,
'location_id': picking_id.location_id.id,
'location_dest_id': picking_id.location_dest_id.id,
'lot_id': stock_lot.id,
'install_tool_time': fields.Datetime.now(),
'qty_done': 1,
'functional_tool_name_id': functional_tool_assembly.id,
'functional_tool_type_id': obj.functional_tool_type_id.id,
'diameter': obj.after_assembly_functional_tool_diameter,
'knife_tip_r_angle': obj.after_assembly_knife_tip_r_angle,
'code': obj.code,
'rfid': obj.rfid,
'functional_tool_name': obj.after_assembly_functional_tool_name,
'tool_groups_id': obj.after_tool_groups_id.id
})
# 将刀具组装入库单的状态更改为就绪
picking_id.action_confirm()
picking_id.button_validate()
def _get_name_stock(self, picking_type_id):
name = picking_type_id.sequence_id.prefix + str(
datetime.strptime(str(fields.Date.today()), "%Y-%m-%d").strftime("%Y%m%d"))
stock_id = self.env['stock.picking'].sudo().search(
[('name', 'like', name), ('picking_type_id', '=', picking_type_id.id)],
limit=1,
order="id desc"
)
if not stock_id:
num = "%03d" % 1
else:
m = int(stock_id.name[-3:]) + 1
num = "%03d" % m
return name + str(num)
def create_tool_stocking_picking1(self, obj):
"""
创建刀具物料出库单
"""
# 获取名称为内部调拨的作业类型
picking_type_id = self.env['stock.picking.type'].sudo().search([('name', '=', '内部调拨')])
# 创建刀具物料出库单
picking_id = self.env['stock.picking'].create({
'name': self._get_name_stock1(picking_type_id),
'picking_type_id': picking_type_id.id,
'location_id': self.env['stock.location'].search([('name', '=', '刀具房')]).id,
'location_dest_id': self.env['stock.location'].search([('name', '=', '刀具组装位置')]).id,
'origin': obj.assembly_order_code
})
# =============刀具物料出库===================
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:
datas['data'].append(
{'current_location_id': obj.integral_freight_barcode_id, 'lot_id': obj.integral_freight_lot_id.lot_id})
if obj.blade_product_id:
datas['data'].append(
{'current_location_id': obj.blade_freight_barcode_id, 'lot_id': obj.blade_freight_lot_id.lot_id})
if obj.bar_product_id:
datas['data'].append(
{'current_location_id': obj.bar_freight_barcode_id, 'lot_id': obj.bar_freight_lot_id.lot_id})
if obj.pad_product_id:
datas['data'].append(
{'current_location_id': obj.pad_freight_barcode_id, 'lot_id': obj.pad_freight_lot_id.lot_id})
if obj.chuck_product_id:
datas['data'].append(
{'current_location_id': obj.chuck_freight_barcode_id, 'lot_id': obj.chuck_freight_lot_id.lot_id})
# 创建刀具物料出库库存移动记录
stock_move_id.create_tool_material_stock_moves(datas)
# 将刀具物料出库库单的状态更改为就绪
picking_id.action_confirm()
# 修改刀具物料出库移动历史记录
stock_move_id.write_tool_material_stock_move_lines(datas)
# 设置数量,并验证完成
picking_id.action_set_quantities_to_reservation()
picking_id.button_validate()
logging.info(f'刀具物料调拨单状态:{picking_id.state}')
def _get_name_stock1(self, picking_type_id):
name = f'{picking_type_id.sequence_id.prefix}DJ/{date.today().strftime("%y")}'
stock_id = self.env['stock.picking'].sudo().search(
[('name', 'like', name), ('picking_type_id', '=', picking_type_id.id)],
limit=1,
order="id desc"
)
if not stock_id:
num = "%05d" % 1
else:
m = int(stock_id.name[-5:]) + 1
num = "%05d" % m
return name + str(num)
class StockMove(models.Model):
_inherit = 'stock.move'
def create_tool_material_stock_moves(self, datas):
picking_id = datas['picking_id']
data = datas['data']
stock_move_ids = []
for res in data:
if res:
# 创建库存移动记录
stock_move_id = self.env['stock.move'].sudo().create({
'name': picking_id.name,
'picking_id': picking_id.id,
'product_id': res['lot_id'].product_id.id,
'location_id': picking_id.location_id.id,
'location_dest_id': picking_id.location_dest_id.id,
'product_uom_qty': 1.00,
'reserved_availability': 1.00
})
stock_move_ids.append(stock_move_id)
return stock_move_ids
def write_tool_material_stock_move_lines(self, datas):
picking_id = datas['picking_id']
data = datas['data']
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:
move_line_id.write({
'current_location_id': res.get('current_location_id').id,
'lot_id': res.get('lot_id').id
})
return True
class ProductProduct(models.Model):
_inherit = 'product.product'
def create_assemble_warehouse_receipt(self, tool_assembly_order_id, functional_tool_assembly, obj):
"""
创建功能刀具批次/序列号记录
"""
product_id = self.env['product.product'].search([('categ_type', '=', '功能刀具'), ('tracking', '=', 'serial')])
if not product_id:
logging.info('没有搜索到功能刀具产品:%s' % product_id)
raise ValidationError('没有找到按唯一序列号追溯的功能刀具产品信息!')
stock_lot = self.env['stock.lot'].create({
'name': self.get_stock_lot_name(obj),
'product_id': product_id[0].id,
'company_id': self.env.company.id
})
return stock_lot
def get_stock_lot_name(self, obj):
"""
生成功能刀具序列号
"""
company = obj.cutting_tool_cutterhandle_model_id.code.split('-', 1)[0]
new_time = datetime.strptime(str(fields.Date.today()), "%Y-%m-%d").strftime("%Y%m%d")
code = '%s-GNDJ-%s-%s' % (company, obj.after_assembly_functional_tool_type_id.code, new_time)
stock_lot_id = self.env['stock.lot'].sudo().search(
[('name', 'like', code)], limit=1, order="id desc")
if not stock_lot_id:
num = "%03d" % 1
else:
m = int(stock_lot_id.name[-3:]) + 1
num = "%03d" % m
return '%s-%s' % (code, num)

View File

@@ -427,10 +427,10 @@
</group>
</sheet>
<footer>
<button string="确定" name="functional_tool_assembly" type="object" class="btn-primary"
attrs="{'invisible': [('obtain_measurement_status', '=', False),('enable_tool_presetter', '=', True)]}"
confirm="是否确认申请组装"/>
<button string="取消" class="btn-secondary" special="cancel"/>
<!-- <button string="确定" name="functional_tool_assembly" type="object" class="btn-primary"-->
<!-- attrs="{'invisible': [('obtain_measurement_status', '=', False),('enable_tool_presetter', '=', True)]}"-->
<!-- confirm="是否确认申请组装"/>-->
<!-- <button string="取消" class="btn-secondary" special="cancel"/>-->
</footer>
</form>
</field>

View File

@@ -931,13 +931,6 @@ class SfStockMoveLine(models.Model):
if not record.destination_location_id.product_id:
record.destination_location_id.product_id = record.product_id.id
@api.model_create_multi
def create(self, vals_list):
records = super(SfStockMoveLine, self).create(vals_list)
self.put_shelf_location(records)
return records
class SfStockPicking(models.Model):
_inherit = 'stock.picking'
@@ -1122,6 +1115,12 @@ class SfPickingType(models.Model):
'sf_warehouse.group_sf_stock_manager'
)
def _get_action(self, action_xmlid):
action = super(SfPickingType, self)._get_action(action_xmlid)
if not self.env.user.has_group('base.group_system'):
action['context']['create'] = False
return action
class CustomStockMove(models.Model):
_name = 'stock.move'

View File

@@ -139,5 +139,5 @@ class MrsShelfLocationDataSync(models.Model):
location_id.product_sn_id = False
except Exception as e:
logging.info("捕获错误信息:%s" % e)
logging.info("库区信息同步失败:%s" % e)
raise ValidationError("数据错误导致同步失败,请联系管理员")

View File

@@ -4,6 +4,7 @@
<record model="ir.actions.act_window" id="stock.stock_picking_type_action">
<field name="context">{'search_default_groupby_code':1}</field>
<field name="domain">[('name', '!=', '制造')]</field>
</record>
<record id="view_location_form_sf_inherit" model="ir.ui.view">

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