Compare commits

...

235 Commits

Author SHA1 Message Date
mgw
65c67775fa Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-04-25 16:10:09 +08:00
mgw
8f2d214854 出厂检验报告发布时二次确认 2025-04-25 16:09:50 +08:00
胡尧
699e03ccda Accept Merge Request #2068: (release/release_2.12 -> develop)
Merge Request: 解决冲突

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2042?initial=true
2025-04-23 16:00:13 +08:00
胡尧
3f88b11a18 解决模块更新报错问题 2025-04-23 15:59:37 +08:00
胡尧
b97acfb181 Accept Merge Request #2041: (feature/6684 -> develop)
Merge Request: 取消线上解决bug的改动

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2041?initial=true
2025-04-23 14:24:00 +08:00
胡尧
6767b693f5 取消线上解决bug的改动 2025-04-23 14:22:50 +08:00
胡尧
7b77d846c3 Accept Merge Request #2040: (hotfix/解决采购申请bug -> develop)
Merge Request: 解决创建采购申请报错

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2040?initial=true
2025-04-23 14:21:27 +08:00
廖丹龙
5e3bce1931 Accept Merge Request #2038: (feature/tool_standard_library_process -> develop)
Merge Request: 排程产能限制问题

Created By: @廖丹龙
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2038
2025-04-23 11:25:04 +08:00
liaodanlong
ef5c2649c9 【sf.t-计划】在排程单详情页选择时间段进行排程时未校验日历有效工作时间 2025-04-23 11:24:38 +08:00
禹翔辉
5400c9ec69 Accept Merge Request #2039: (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/2039
2025-04-23 11:22:47 +08:00
yuxianghui
7c7876e96f 工单添加默认按工序排序 2025-04-23 11:19:08 +08:00
liaodanlong
5438ff6df1 排程产能限制问题 2025-04-23 11:06:03 +08:00
胡尧
05b5e9cfd9 解决创建采购申请报错 2025-04-23 10:32:39 +08:00
胡尧
86dfd437cc 解决创建采购申请报错 2025-04-23 10:31:30 +08:00
胡尧
3a92e4cfd8 解决创建采购申请报错 2025-04-23 10:22:20 +08:00
胡尧
bff0ff9401 增加日志 2025-04-23 10:18:49 +08:00
管欢
9b3fe5c070 Accept Merge Request #2036: (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/2036
2025-04-23 09:48:26 +08:00
廖丹龙
92d593bf24 Accept Merge Request #2037: (feature/tool_standard_library_process -> develop)
Merge Request: 采购申请单查询条件同一

Created By: @廖丹龙
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2037
2025-04-23 09:38:27 +08:00
liaodanlong
dc6fb4ddda 采购申请单查询条件同一 2025-04-23 09:34:02 +08:00
廖丹龙
a84f8636a4 Accept Merge Request #2035: (feature/tool_standard_library_process -> develop)
Merge Request: 工艺确认报错问题处理

Created By: @廖丹龙
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2035
2025-04-23 09:12:59 +08:00
liaodanlong
7c26045377 工艺确认报错问题处理 2025-04-23 09:12:20 +08:00
胡尧
da579a15b3 Accept Merge Request #2034: (feature/6694 -> develop)
Merge Request: 修复bug

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2034?initial=true
2025-04-23 08:53:23 +08:00
胡尧
c9ee0af25a 修复bug 2025-04-23 08:52:49 +08:00
guanhuan
151bc5da4f 报价菜单修改 2025-04-22 15:43:14 +08:00
胡尧
1015af483c Accept Merge Request #2033: (feature/6694 -> develop)
Merge Request: 修改ftp传输代码

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2033?initial=true
2025-04-22 15:23:29 +08:00
胡尧
bb3971c93e 修改ftp传输代码 2025-04-22 15:23:02 +08:00
马广威
aa9efe932e Accept Merge Request #2032: (feature/制造功能优化 -> develop)
Merge Request: 处理浮点型增0问题

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2032
2025-04-22 15:13:28 +08:00
廖丹龙
8e09e9715c Accept Merge Request #2031: (feature/tool_standard_library_process -> develop)
Merge Request: 空值处理

Created By: @廖丹龙
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2031
2025-04-22 15:05:42 +08:00
mgw
30a55658de 处理浮点型增0问题 2025-04-22 15:02:38 +08:00
liaodanlong
7ecdc31db4 空值处理 2025-04-22 15:00:54 +08:00
廖丹龙
4234a6bbd9 Accept Merge Request #2030: (feature/tool_standard_library_process -> develop)
Merge Request: 设置字段必填

Created By: @廖丹龙
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2030
2025-04-22 14:28:29 +08:00
liaodanlong
8fcc436ce5 设置字段必填 2025-04-22 14:24:10 +08:00
liaodanlong
53a676bc93 查询条件修改 2025-04-22 14:22:31 +08:00
廖丹龙
c6f625fe44 Accept Merge Request #2027: (feature/tool_standard_library_process -> develop)
Merge Request: 工艺相关展示调整

Created By: @廖丹龙
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2027
2025-04-22 13:49:59 +08:00
liaodanlong
397c35867b 产品设置默认供应商数据 2025-04-22 13:41:22 +08:00
liaodanlong
f843610872 采购申请处理 2025-04-22 13:40:15 +08:00
liaodanlong
41e4d2656a 采购申请单 2025-04-22 13:39:57 +08:00
liaodanlong
4272d2855f 坯料自动勾选工艺路线 2025-04-22 13:38:50 +08:00
马广威
e63a12cbc1 Accept Merge Request #2029: (feature/制造功能优化 -> develop)
Merge Request: 调整上传文件名

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2029?initial=true
2025-04-22 13:17:03 +08:00
mgw
2867264e88 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-04-22 13:16:34 +08:00
mgw
7c2ddd8a0c 调整上传文件名 2025-04-22 13:16:15 +08:00
马广威
4daddcff61 Accept Merge Request #2028: (feature/制造功能优化 -> develop)
Merge Request: 调整适配零件图号为数字

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2028?initial=true
2025-04-22 11:51:29 +08:00
mgw
666c0167d7 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-04-22 11:50:33 +08:00
mgw
6b7f1aedbe 调整适配零件图号为数字 2025-04-22 11:50:15 +08:00
mgw
4234494f08 调整可用率 2025-04-22 10:55:21 +08:00
liaodanlong
cbc73ee8e3 工艺相关展示调整 2025-04-21 16:04:36 +08:00
黄焱
ec3d2a9239 Accept Merge Request #2026: (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/2026?initial=true
2025-04-21 16:04:32 +08:00
hyyy
a55da93dc4 修复样式问题 2025-04-21 16:01:04 +08:00
廖丹龙
73dce5e75e Accept Merge Request #2025: (feature/tool_standard_library_process -> develop)
Merge Request: 外协工单采购申请查询问题

Created By: @廖丹龙
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2025
2025-04-21 15:00:30 +08:00
liaodanlong
9b009ec5a3 外协工单采购申请查询问题 2025-04-21 14:55:52 +08:00
liaodanlong
7d70184a5d 外协工单采购申请查询问题 2025-04-21 14:55:20 +08:00
liaodanlong
9f9c08b1f0 制造订单采购申请查询问题 2025-04-21 14:35:57 +08:00
廖丹龙
5886b4e132 Accept Merge Request #2024: (feature/tool_standard_library_process -> develop)
Merge Request: 制造订单报错问题

Created By: @廖丹龙
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2024
2025-04-21 13:18:49 +08:00
liaodanlong
77478fd173 制造订单报错问题 2025-04-21 13:16:15 +08:00
胡尧
2154533ad5 Accept Merge Request #2023: (feature/6694 -> develop)
Merge Request: 去掉打印模块的type字段

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2023?initial=true
2025-04-21 13:15:22 +08:00
胡尧
eab7bf04c3 去掉打印模块的type字段 2025-04-21 13:15:02 +08:00
马广威
ba910f011e Accept Merge Request #2022: (feature/制造功能优化 -> develop)
Merge Request: sf-质量检查-上传报告校验必填,及上传时检查图号跟产品的零件图号是否一致

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2022?initial=true
2025-04-21 12:22:20 +08:00
mgw
c43d8ee280 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-04-21 12:20:56 +08:00
mgw
acc04f396d sf-质量检查-上传报告校验必填,及上传时检查图号跟产品的零件图号是否一致 2025-04-21 12:20:37 +08:00
胡尧
3425376705 Accept Merge Request #2021: (feature/6763 -> develop)
Merge Request: 工单列表增加模型ID搜索

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2021?initial=true
2025-04-21 11:26:29 +08:00
胡尧
7849ce6342 工单列表增加模型ID搜索 2025-04-21 11:25:37 +08:00
liaodanlong
69e8bf67d1 sf-制造-表面工艺可选参数-从cloud同步过来的数据,获取方式方式=外协 2025-04-21 10:04:59 +08:00
廖丹龙
722bdc251d Accept Merge Request #2020: (feature/tool_standard_library_process -> develop)
Merge Request: 开放分组类型限制工艺参数字段显示

Created By: @廖丹龙
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2020
2025-04-21 09:53:27 +08:00
liaodanlong
0403a2d223 开放分组类型限制工艺参数字段显示 2025-04-21 09:52:52 +08:00
liaodanlong
7a4c3f155c 开放分组类型限制工艺参数字段显示 2025-04-21 09:52:24 +08:00
胡尧
acd8d9c758 Accept Merge Request #2019: (feature/6694 -> develop)
Merge Request: 去掉多余的代码

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2019?initial=true
2025-04-21 09:23:48 +08:00
廖丹龙
d33a801f89 Accept Merge Request #2018: (feature/tool_standard_library_process -> develop)
Merge Request: 字段名写错

Created By: @廖丹龙
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2018
2025-04-21 09:20:29 +08:00
liaodanlong
a74c07f0bb 字段名写错 2025-04-21 09:19:36 +08:00
胡尧
9f1cac0789 去掉多余的代码 2025-04-21 09:10:20 +08:00
廖丹龙
fcd0319a9c Accept Merge Request #2017: (feature/tool_standard_library_process -> develop)
Merge Request: 工厂日历独立菜单到计划模块

Created By: @廖丹龙
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2017
2025-04-18 17:29:28 +08:00
liaodanlong
ed2c76adf1 工厂日历独立菜单到计划模块 2025-04-18 17:24:41 +08:00
廖丹龙
1d45486e20 Accept Merge Request #2016: (feature/tool_standard_library_process -> develop)
Merge Request: sf-制造-表面工艺-菜单名称修改

Created By: @廖丹龙
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2016
2025-04-18 16:46:38 +08:00
liaodanlong
d209d40356 sf-制造-表面工艺-菜单名称修改 2025-04-18 16:39:09 +08:00
胡尧
3355731632 Accept Merge Request #2015: (feature/6694 -> develop)
Merge Request: 增加打印模块,增加工单下发后打印程序单

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2015?initial=true
2025-04-18 16:07:01 +08:00
liaodanlong
ddd902cad8 修改创建表面工艺产品时表面工艺参数不能为空的问题 2025-04-18 16:06:08 +08:00
liaodanlong
e5566edd7c sf_同步cloud表面工艺可选参数创建产品,修改产品名命名规则 2025-04-18 15:45:32 +08:00
liaodanlong
ea0f9d6ee2 sf-制造-作业-表单详情内,可选工艺参数手动新增编码规则跟外协服务产品名称不正确 2025-04-18 15:45:00 +08:00
liaodanlong
79006e062a 1.限制工序-作业配置 产品只能选中一个
2.添加产品下拉框查询过滤
3.产品选择框可以跳转到产品页面
2025-04-18 15:44:13 +08:00
胡尧
ee9783d4a6 增加打印模块,增加工单下发后打印程序单 2025-04-18 14:50:03 +08:00
马广威
ff7c4a9ce7 Accept Merge Request #2014: (feature/制造功能优化 -> develop)
Merge Request: 调整设备可用率接口

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2014?initial=true
2025-04-18 14:40:31 +08:00
mgw
48b94e8d84 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-04-18 14:39:11 +08:00
mgw
a395994a1c 调整设备可用率接口 2025-04-18 14:38:39 +08:00
mgw
43c29e30b0 暂时注释稼动率接口 2025-04-18 14:18:41 +08:00
管欢
be639d7631 Accept Merge Request #2013: (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/2013
2025-04-18 14:08:29 +08:00
guanhuan
66c745af3c 排序优化 2025-04-18 13:22:25 +08:00
mgw
29ee275840 增加上传支持格式 2025-04-18 13:00:45 +08:00
管欢
f92b844c16 Accept Merge Request #2012: (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/2012
2025-04-18 09:50:58 +08:00
廖丹龙
63870ed7a9 Accept Merge Request #2011: (feature/tool_standard_library_process -> develop)
Merge Request: 产品表面工艺参数更名为工艺参数

Created By: @廖丹龙
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2011
2025-04-18 09:07:24 +08:00
liaodanlong
558c99f48b 产品表面工艺参数更名为工艺参数 2025-04-18 09:00:06 +08:00
廖丹龙
1264a4305e Accept Merge Request #2010: (feature/tool_standard_library_process -> develop)
Merge Request: 更新工艺外协流程的采购单查询逻辑

Created By: @廖丹龙
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2010
2025-04-17 17:13:54 +08:00
liaodanlong
a4f8183654 更新工艺外协流程的采购单查询逻辑 2025-04-17 17:02:43 +08:00
廖丹龙
3ea8e9d35f Accept Merge Request #2009: (feature/tool_standard_library_process -> develop)
Merge Request: 更新工艺外协流程的报错问题

Created By: @廖丹龙
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2009
2025-04-17 16:34:27 +08:00
liaodanlong
3fb2890bf1 更新工艺外协流程的报错问题 2025-04-17 16:32:18 +08:00
廖丹龙
0c3ba5c172 Accept Merge Request #2008: (feature/tool_standard_library_process -> develop)
Merge Request: 外协流程更改

Created By: @廖丹龙
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2008
2025-04-17 14:39:18 +08:00
liaodanlong
1b22d02700 Merge branch 'refs/heads/develop' into feature/tool_standard_library_process
# Conflicts:
#	sf_manufacturing/models/__init__.py
2025-04-17 14:35:24 +08:00
liaodanlong
962931bc9d 外协流程更改 2025-04-17 14:29:06 +08:00
guanhuan
9cfc8418ec 优化库存排序 2025-04-17 13:44:25 +08:00
胡尧
e4af2aacb8 Accept Merge Request #2007: (feature/6694 -> develop)
Merge Request: 增加工单下发时打印编程文件pdf的功能

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2007?initial=true
2025-04-17 13:18:03 +08:00
胡尧
c67944f689 增加工单下发时打印编程文件pdf的功能 2025-04-17 13:15:48 +08:00
胡尧
9c713c2a37 设备增加网络配置,增加编程文件传输接口 2025-04-16 17:21:36 +08:00
mgw
53d4be596f 调整日志 2025-04-16 11:26:21 +08:00
mgw
195302b67f 调整月合格率 2025-04-16 11:20:35 +08:00
mgw
6cb5e909d4 调整稼动率 2025-04-16 11:12:04 +08:00
胡尧
cb98f5cf25 Accept Merge Request #2006: (feature/6609 -> develop)
Merge Request: 产品主数据【条码】存储模型ID

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2006?initial=true
2025-04-16 09:30:28 +08:00
胡尧
f87a6690fe Accept Merge Request #2005: (feature/6684 -> develop)
Merge Request: 修改sf定时任务不在部署时更新配置

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2005?initial=true
2025-04-16 09:29:59 +08:00
胡尧
b041feb297 Merge branch 'release/release_2.11' 2025-04-15 20:54:53 +08:00
禹翔辉
06e9d5a538 Accept Merge Request #2004: (feature/功能刀具拆解单优化_1 -> develop)
Merge Request: Merge branch 'feature/功能刀具拆解单优化' into feature/功能刀具拆解单优化_1

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2004
2025-04-15 17:22:50 +08:00
yuxianghui
b9aac6c558 Merge branch 'feature/功能刀具拆解单优化' into feature/功能刀具拆解单优化_1 2025-04-15 17:21:31 +08:00
yuxianghui
2de0e9f02f 处理功能刀具预警没有自动创建预警记录和拆解单问题 2025-04-15 17:20:16 +08:00
胡尧
722b601890 Accept Merge Request #2003: (release/release_2.11 -> develop)
Merge Request: 修改质检单的质检员字段

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2003?initial=true
2025-04-15 17:14:51 +08:00
胡尧
20722c12b8 修改质检单的质检员字段 2025-04-15 17:08:43 +08:00
胡尧
2100ee9590 增加备注 2025-04-15 17:08:17 +08:00
禹翔辉
56f2ea0356 Accept Merge Request #2002: (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/2002
2025-04-15 16:58:28 +08:00
胡尧
6ff8395916 增加向pdf中添加二维码的功能 2025-04-15 16:57:32 +08:00
yuxianghui
bc85c457ad 功能刀具添加状态变更跟踪 2025-04-15 16:46:49 +08:00
胡尧
085e6359ce 编程单增加二维码 2025-04-15 15:47:35 +08:00
禹翔辉
7eeea92a3e Accept Merge Request #2001: (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/2001
2025-04-15 15:09:36 +08:00
yuxianghui
d672f3f4d7 功能刀具拆解单添加可以选择线边刀库的功能刀具进行拆解 2025-04-15 15:05:23 +08:00
黄焱
c79cf2e5ad Accept Merge Request #2000: (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/2000?initial=true
2025-04-15 11:26:42 +08:00
hyyy
33a5fc0ff4 删除多余字段 2025-04-15 11:24:45 +08:00
黄焱
2dcaa25952 Accept Merge Request #1999: (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/1999
2025-04-15 09:43:20 +08:00
hyyy
ba88070fad 修改刀片刀杆样式 2025-04-15 09:31:42 +08:00
马广威
3f940992be Accept Merge Request #1998: (feature/制造功能优化 -> develop)
Merge Request: 调整报告,增加稼动率接口(暂未调用)

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1998?initial=true
2025-04-14 17:24:04 +08:00
mgw
197ae6bc01 去掉基础数据 2025-04-14 17:22:27 +08:00
mgw
07336326ce 优化稼动率接口 2025-04-14 17:16:38 +08:00
mgw
c93553e78e 增加稼动率接口 2025-04-14 16:42:33 +08:00
liaodanlong
0db31f7936 Merge branch 'refs/heads/develop' into feature/tool_standard_library_process 2025-04-14 15:24:22 +08:00
liaodanlong
61a3cf606e 表面工艺数据内置与数据同步调整 2025-04-14 15:23:47 +08:00
mgw
91d79008e1 故障时长提取 2025-04-14 11:21:24 +08:00
禹翔辉
8cdf77f609 Accept Merge Request #1997: (feature/功能刀具组装初始化优化 -> 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/1997
2025-04-14 10:34:47 +08:00
yuxianghui
37edc858c2 1 2025-04-14 10:32:26 +08:00
yuxianghui
17fdf20e03 功能刀具组装自动根据BOM配置初始物料信息优化 2025-04-14 10:30:51 +08:00
胡尧
a896108638 产品主数据【条码】存储模型ID 2025-04-11 16:49:18 +08:00
胡尧
026697f363 修改sf定时任务不在部署时更新配置 2025-04-11 16:12:58 +08:00
胡尧
ec934abc42 Accept Merge Request #1996: (feature/6679 -> develop)
Merge Request: 给仓储岗增加修改工单的权限

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1996
2025-04-11 16:04:02 +08:00
胡尧
cc030957fb 给仓储岗增加修改工单的权限 2025-04-11 16:02:24 +08:00
mgw
87786dbd80 调整故障时长 2025-04-11 14:16:18 +08:00
禹翔辉
9d0ffd23b2 Accept Merge Request #1995: (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/1995
2025-04-10 14:27:04 +08:00
yuxianghui
3fb56f15c8 处理功能刀具组装时,有时扫描货位编码验证刀具信息扫不到货位的问题 2025-04-10 14:24:25 +08:00
mgw
bdf4696c08 调整待完成工单明细 2025-04-09 16:56:53 +08:00
mgw
6c926bf081 调整质检取值 2025-04-09 16:39:21 +08:00
mgw
ddb0c304b9 修改字段路径 2025-04-09 15:57:13 +08:00
mgw
cf8c14e738 调整查询范围 2025-04-09 15:22:41 +08:00
黄焱
6bd6816495 Accept Merge Request #1994: (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/1994?initial=true
2025-04-09 14:03:06 +08:00
hyyy
2bae98950e 功能刀具组装优化需求 2025-04-09 13:57:48 +08:00
胡尧
ec379a7541 Accept Merge Request #1993: (feature/6679 -> develop)
Merge Request: 增加不同工作中心配置相同接驳站的功能

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1993?initial=true
2025-04-08 17:08:20 +08:00
胡尧
119acf1543 Accept Merge Request #1992: (feature/6686 -> develop)
Merge Request: agv配置不可修改

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1992
2025-04-08 17:07:57 +08:00
禹翔辉
0f6f1aae24 Accept Merge Request #1991: (feature/功能刀具拆解优化_1 -> develop)
Merge Request: 将寿命到期拆解创建的组装单添加到对应安全库存组装单列表

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1991
2025-04-08 17:01:56 +08:00
yuxianghui
c40ecfb6ce 将寿命到期拆解创建的组装单添加到对应安全库存组装单列表 2025-04-08 17:00:00 +08:00
禹翔辉
a51a4c2fbb Accept Merge Request #1990: (feature/功能刀具拆解优化_1 -> develop)
Merge Request: 功能刀具拆解寿命到期刀具创建新的组装单时,添加几个参数的值。

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1990?initial=true
2025-04-08 15:59:04 +08:00
yuxianghui
315e2aa03d 功能刀具拆解寿命到期刀具创建新的组装单时,添加几个参数的值。 2025-04-08 15:56:08 +08:00
胡尧
10bea40159 增加不同工作中心配置相同接驳站的功能 2025-04-08 10:29:29 +08:00
马广威
78ba8d0ead Accept Merge Request #1989: (feature/制造功能优化 -> develop)
Merge Request: 调整报告页码

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1989
2025-04-08 10:11:41 +08:00
mgw
e61742cc5b Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-04-08 10:11:05 +08:00
mgw
d0d1a640d9 调整报告页码 2025-04-08 10:10:36 +08:00
禹翔辉
e613a2f283 Accept Merge Request #1987: (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/1987
2025-04-08 09:59:09 +08:00
马广威
88e83c0e14 Accept Merge Request #1988: (feature/制造功能优化 -> develop)
Merge Request: 调整报告产品取值

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1988
2025-04-08 09:48:48 +08:00
mgw
f912a81e7b 调整报告产品取值 2025-04-08 09:48:05 +08:00
yuxianghui
e09226e966 新增对寿命未到期且位置在线边刀库的功能刀具进行拆解时,不直接报错,而是进行二次确认是否进行拆除。 2025-04-08 09:44:46 +08:00
胡尧
8a7a90ff0d agv配置不可修改 2025-04-07 17:04:34 +08:00
胡尧
d05a3606c3 Merge branch 'release/release_2.10' 2025-04-02 11:50:07 +08:00
yuxianghui
4b5c3b383b 对工单的page页进行调整 2025-04-02 10:58:25 +08:00
胡尧
1c34f2f15c Merge branch 'release/release_2.10' 2025-04-02 09:08:47 +08:00
胡尧
fd5ff0904e Merge branch 'release/release_2.10' 2025-04-01 20:43:29 +08:00
胡尧
3d355ec303 Merge branch 'release/release_2.9' 2025-03-04 09:16:48 +08:00
胡尧
d3a1e9b341 Merge branch 'release/release_2.8' 2025-01-21 10:43:09 +08:00
胡尧
8db80e1ed6 Merge branch 'release/release_2.7' 2025-01-07 20:16:29 +08:00
胡尧
fc378df597 Merge branch 'release/release_2.6' 2024-12-08 19:56:05 +08:00
胡尧
0575d89227 Merge branch 'release/release_2.5' 2024-11-04 19:49:05 +08:00
胡尧
39afc99b8f Merge branch 'release/release_2.4' 2024-10-11 09:29:34 +08:00
杨金灵
f230ad55fb Accept Merge Request #1284: (hotfix/修复报废-外协入库验证 -> master)
Merge Request: 修复报废-外协入库验证

Created By: @杨金灵
Accepted By: @杨金灵
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1284
2024-09-06 13:47:55 +08:00
113 changed files with 3111 additions and 618 deletions

View File

@@ -157,11 +157,11 @@ td.o_required_modifier {
color: #aaa;
}
.o_kanban_primary_left {
display: flex;
flex-direction: row-reverse;
justify-content: flex-start;
}
// .o_kanban_primary_left {
// display: flex;
// flex-direction: row-reverse;
// justify-content: flex-start;
// }
.o_list_view .o_list_table thead {
position: sticky;

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,56 @@
from io import BytesIO
import qrcode
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4
from PIL import Image
from reportlab.lib.utils import ImageReader
from odoo import models, fields, api
class JikimoPrinting(models.AbstractModel):
_name = 'jikimo.printing'
def print_qr_code(self, data):
"""
打印二维码
"""
# 生成二维码
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(data)
qr.make(fit=True)
qr_image = qr.make_image(fill_color="black", back_color="white")
# 将PIL Image转换为reportlab可用的格式
temp_image = BytesIO()
qr_image.save(temp_image, format="PNG")
temp_image.seek(0)
# 创建PDF
pdf_buffer = BytesIO()
c = canvas.Canvas(pdf_buffer, pagesize=A4)
# 计算位置
a4_width, a4_height = A4
qr_width = 200
qr_height = 200
x = (a4_width - qr_width) / 2
y = (a4_height - qr_height) / 2
# 直接从BytesIO绘制图片
c.drawImage(ImageReader(Image.open(temp_image)), x, y, width=qr_width, height=qr_height)
c.save()
# 获取PDF内容并打印
pdf_content = pdf_buffer.getvalue()
printer = self.env['printing.printer'].get_default()
printer.print_document(report=None, content=pdf_content, doc_format='pdf')
# 清理资源
pdf_buffer.close()
temp_image.close()
def print_pdf(self, pdf_data):
"""
打印PDF
"""
printer = self.env['printing.printer'].get_default()
printer.print_document(report=None, content = pdf_data, doc_format='pdf')

View File

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

View File

@@ -0,0 +1,33 @@
import logging
from odoo import models, fields, api
_logger = logging.getLogger(__name__)
class MrpWorkorder(models.Model):
_name = 'mrp.workorder'
_inherit = ['mrp.workorder']
def _compute_state(self):
super(MrpWorkorder, self)._compute_state()
for workorder in self:
work_ids = workorder.production_id.workorder_ids.filtered(lambda w: w.routing_type == '装夹预调' or w.routing_type == '人工线下加工')
for wo in work_ids:
if wo.state == 'ready' and not wo.production_id.product_id.is_print_program:
# 触发打印程序
pdf_data = workorder.processing_drawing
if pdf_data:
try:
# 执行打印
self.env['jikimo.printing'].print_pdf(pdf_data)
wo.production_id.product_id.is_print_program = True
_logger.info(f"工单 {wo.name} 的PDF已成功打印")
except Exception as e:
_logger.error(f"工单 {wo.name} 的PDF打印失败: {str(e)}")
class ProductTemplate(models.Model):
_inherit = 'product.template'
is_print_program = fields.Boolean(string='是否打印程序', default=False)

View File

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

View File

@@ -9,7 +9,7 @@ class MrpProduction(models.Model):
@api.depends('state')
def _compute_pr_mp_count(self):
for item in self:
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name)])
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name), ('is_subcontract', '!=', 'True')])
if pr_ids:
item.pr_mp_count = len(pr_ids)
else:
@@ -20,7 +20,7 @@ class MrpProduction(models.Model):
采购请求
"""
self.ensure_one()
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name)])
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name),('is_subcontract', '!=', True)])
action = {
'res_model': 'purchase.request',
'type': 'ir.actions.act_window',

View File

@@ -32,6 +32,7 @@ class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
line.company_id,
line.request_id.origin,
)
# po_data.update({'related_product':line.related_product.id})
purchase = purchase_obj.create(po_data)
# Look for any other PO line in the selected PO with same
@@ -63,6 +64,8 @@ class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
po_line_data = self._prepare_purchase_order_line(purchase, item)
if item.keep_description:
po_line_data["name"] = item.name
if line.related_product:
po_line_data.update({'related_product': line.related_product.id})
po_line = po_line_obj.create(po_line_data)
po_line_product_uom_qty = po_line.product_uom._compute_quantity(
po_line.product_uom_qty, alloc_uom
@@ -100,6 +103,7 @@ class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
def check_group(self, request_lines):
# 去掉合并必须同一采购组的限制
pass
class PurchaseRequestLineMakePurchaseOrderItem(models.TransientModel):

View File

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

View File

@@ -0,0 +1,17 @@
# -*- coding: utf-8 -*-
{
'name': '机企猫 报工系统API',
'version': '1.0.0',
'summary': """ 机企猫 报工系统API """,
'author': '机企猫',
'website': 'https://xt.sf.jikimo.com',
'category': 'sf',
'depends': ['base', 'sf_maintenance', 'jikimo_mini_program'],
'data': [
],
'application': True,
'installable': True,
'auto_install': False,
'license': 'LGPL-3',
}

View File

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

View File

@@ -0,0 +1,69 @@
import json
from odoo import http
from odoo.http import request
from odoo.addons.sf_machine_connect.models.ftp_operate import transfer_files
from odoo.addons.sf_base.decorators.api_log import api_log
class MainController(http.Controller):
@http.route('/api/manual_download_program', type='json', methods=['POST'], auth='public', cors='*')
@api_log('人工线下加工编程文件传输', requester='报工系统')
def manual_download_program(self):
"""
人工线下加工传输编程文件
"""
data = json.loads(request.httprequest.data)
maintenance_equipment_id = data.get('maintenance_equipment_id')
model_id = data.get('model_id')
if not maintenance_equipment_id or not model_id:
return {'code': 400, 'message': '参数错误'}
try:
model_id = int(model_id)
except Exception as e:
return {'code': 400, 'message': '参数类型错误'}
maintenance_equipment = request.env['maintenance.equipment'].sudo().search(
[('MTcode', '=', maintenance_equipment_id), ('category_id.equipment_type', '=', '机床')],
limit=1
)
if not maintenance_equipment:
return {'code': 400, 'message': '机台不存在,请扫描正确的机台二维码'}
product = request.env['product.template'].sudo().search([('model_id', '=', model_id)], limit=1)
if not product:
return {'code': 400, 'message': '请扫描正确的图纸'}
# 获取刀具组
tool_groups_id = request.env['sf.tool.groups'].sudo().search([('equipment_ids', 'in', maintenance_equipment.id)], limit=1)
if not tool_groups_id:
return {'code': 400, 'message': '刀具组不存在'}
ftp_resconfig = request.env['res.config.settings'].sudo().get_values()
if not ftp_resconfig['ftp_host'] or not ftp_resconfig['ftp_port'] or not ftp_resconfig['ftp_user'] or not ftp_resconfig['ftp_password']:
return {'code': 400, 'message': '编程文件FTP配置错误'}
source_ftp_info = {
'host': ftp_resconfig['ftp_host'],
'port': int(ftp_resconfig['ftp_port']),
'username': ftp_resconfig['ftp_user'],
'password': ftp_resconfig['ftp_password']
}
if not maintenance_equipment.ftp_host or not maintenance_equipment.ftp_port or not maintenance_equipment.ftp_username or not maintenance_equipment.ftp_password:
return {'code': 400, 'message': '机台FTP配置错误'}
target_ftp_info = {
'host': maintenance_equipment.ftp_host,
'port': int(maintenance_equipment.ftp_port),
'username': maintenance_equipment.ftp_username,
'password': maintenance_equipment.ftp_password
}
# 传输nc文件
try:
result = transfer_files(
source_ftp_info,
target_ftp_info,
'/' + str(model_id),
'/',
match_str=r'^\d*_\d*-' + tool_groups_id.name + r'-\w{2}-all\.nc$'
)
if len(result) > 0:
return {'code': 200, 'message': '传输成功', 'file_list': result}
else:
return {'code': 404, 'message': '未找到编程文件'}
except Exception as e:
return {'code': 500, 'message': str(e)}

View File

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

View File

@@ -52,10 +52,10 @@ class JikimoWorkorderException(models.Model):
def _get_message(self, message_queue_ids):
contents, _ = super(JikimoWorkorderException, self)._get_message(message_queue_ids)
url = self.env['ir.config_parameter'].get_param('web.base.url')
base_url = self.env['ir.config_parameter'].get_param('web.base.url')
action_id = self.env.ref('mrp.mrp_production_action').id
for index, content in enumerate(contents):
exception_id = self.env['jikimo.workorder.exception'].browse(message_queue_ids[index].res_id)
url = url + '/web#id=%s&view_type=form&action=%s' % (exception_id.workorder_id.production_id.id, action_id)
url = base_url + '/web#id=%s&view_type=form&action=%s' % (exception_id.workorder_id.production_id.id, action_id)
contents[index] = content.replace('{{url}}', url)
return contents, message_queue_ids

View File

@@ -141,7 +141,7 @@ class QualityCheck(models.Model):
# # 出厂检验报告编号
# report_number = fields.Char('出厂检验报告编号', compute='_compute_report_number', readonly=True)
# 总数量值为调拨单_产品明细_数量
total_qty = fields.Char('总数量', compute='_compute_total_qty')
total_qty = fields.Char('总数量', compute='_compute_total_qty', readonly=False, store=True)
column_nums = fields.Integer('测量值列数', default=1)
@@ -206,7 +206,7 @@ class QualityCheck(models.Model):
('NG', 'NG')
], string='出厂检验报告结果', default='OK')
measure_operator = fields.Many2one('res.users', string='操机员')
quality_manager = fields.Many2one('res.users', string='质检员', compute='_compute_quality_manager', store=True)
quality_manager = fields.Many2one('res.users', string='质检员', compute='_compute_quality_manager')
@api.depends('measure_line_ids')
def _compute_quality_manager(self):
@@ -258,13 +258,65 @@ class QualityCheck(models.Model):
line[field_name] = False
self.column_nums = self.column_nums - 1
def upload_measure_line(self):
"""
上传测量值
"""
for record in self:
if not record.part_name or not record.part_number:
raise UserError(_('零件名称和零件图号均不能为空'))
# 如果验证通过,返回原动作
action = self.env.ref('quality_control.import_complex_model_wizard').read()[0]
action['context'] = {
'default_model_name': 'quality.check.measure.line',
'default_check_id': self.id,
}
return action
def do_preview(self):
"""
预览出厂检验报告
"""
pass
def check_total_quality(self):
"""
校验总数量是否等于拣货数量
"""
for record in self:
if record.picking_id:
for move in record.picking_id.move_ids_without_package:
if move.product_id == record.product_id:
picking_qty = int(move.product_uom_qty)
if int(record.total_qty) != picking_qty:
return True
return False
def do_publish(self):
"""打开二次确认弹窗"""
self.ensure_one()
if self.check_total_quality():
return self._show_confirmation_wizard()
else:
return self._do_actual_publish()
def _show_confirmation_wizard(self):
"""显示确认向导的通用方法"""
return {
'name': _('发布确认'),
'type': 'ir.actions.act_window',
'res_model': 'quality.check.publish.wizard',
'view_mode': 'form',
'target': 'new',
'context': {
'default_check_id': self.id,
# 其他默认字段...
}
}
def _do_actual_publish(self):
"""发布出厂检验报告"""
self.ensure_one()
self._check_part_number()

View File

@@ -267,7 +267,7 @@
<field name="company_id" invisible="1"/>
<field name="categ_type" invisible="1"/>
<field name="product_id" attrs="{'invisible' : [('measure_on', '=', 'operation')]}"/>
<field name="part_name" attrs="{'invisible': [('categ_type', '!=', '成品')]}"/>
<field name="part_name" attrs="{'invisible': [('categ_type', '!=', '成品')], 'readonly': [('publish_status', '=', 'published')]}"/>
<field name="part_number" attrs="{'invisible': [('categ_type', '!=', '成品')], 'readonly': [('publish_status', '=', 'published')]}"/>
<field name="material_name" attrs="{'invisible': [('categ_type', '!=', '成品')]}"/>
<field name="total_qty" attrs="{'invisible': ['|', ('measure_on', '!=', 'product'), ('is_out_check', '=', False)]}"/>
@@ -334,11 +334,15 @@
<div class="o_row">
<button name="add_measure_line" type="object" class="btn-primary" string="添加测量值" attrs="{'invisible': [('publish_status', '=', 'published')]}"/>
<button name="remove_measure_line" type="object" class="btn-primary" string="删除测量值" attrs="{'invisible': [('publish_status', '=', 'published')]}"/>
<button name="%(quality_control.import_complex_model_wizard)d" string="上传"
type="action"
<!-- <button name="%(quality_control.import_complex_model_wizard)d" string="上传111" -->
<!-- type="action" -->
<!-- class="btn-primary" -->
<!-- attrs="{'force_show':1, 'invisible': [('publish_status', '=', 'published')]}" -->
<!-- context="{'default_model_name': 'quality.check.measure.line', 'default_check_id': id}"/> -->
<button name="upload_measure_line" string="上传"
type="object"
class="btn-primary"
attrs="{'force_show':1, 'invisible': [('publish_status', '=', 'published')]}"
context="{'default_model_name': 'quality.check.measure.line', 'default_check_id': id}"/>
attrs="{'force_show':1, 'invisible': [('publish_status', '=', 'published')]}"/>
</div>
<br/>
<div class="o_row">

View File

@@ -22,6 +22,7 @@ _logger = logging.getLogger(__name__)
class ImportComplexModelWizard(models.TransientModel):
_name = 'quality.check.import.complex.model.wizard'
file_data = fields.Binary("数据文件")
filename = fields.Char(string='文件名')
model_name = fields.Char(string='Model Name')
field_basis = fields.Char(string='Field Basis')
check_id = fields.Many2one(string='质检单', comodel_name='quality.check')
@@ -93,11 +94,16 @@ class ImportComplexModelWizard(models.TransientModel):
return repeats
def convert_float(self, value):
if isinstance(value, float) and value.is_integer():
return int(value)
return value
def import_data(self):
"""导入Excel数据"""
if not self.file_data:
raise UserError(_('请先上传Excel文件'))
if self.check_id.measure_line_ids:
self.sudo().check_id.measure_line_ids.unlink()
@@ -161,6 +167,20 @@ class ImportComplexModelWizard(models.TransientModel):
# 从第二行开始读取数据(跳过表头)
max_columns = 1
for row_index in range(1, sheet.nrows):
row = sheet.row_values(row_index)
# 检查行是否有数据
if not any(row):
continue
if row[2] == '':
continue
logging.info('================%s, %s==' % (row[1], type(row[1])))
compare_value = self.convert_float(row[1])
if str(compare_value) != quality_check.part_number:
print(sheet.row_values(row_index))
raise UserError(_('上传内容图号错误,请修改'))
for row_index in range(1, sheet.nrows):
row = sheet.row_values(row_index)
@@ -177,14 +197,14 @@ class ImportComplexModelWizard(models.TransientModel):
'sequence': len(quality_check.measure_line_ids) + 1,
'product_name': str(row[0]) if row[0] else '', # 产品名称列
'drawing_no': str(row[1]) if row[1] else '', # 图号列
'measure_item': row[2] or '', # 检测项目列
'measure_value1': str(row[4]) if row[4] else '', # 测量值1
'measure_value2': str(row[5]) if row[5] else '', # 测量值2
'measure_value3': str(row[6]) if len(row) > 6 and row[6] else '', # 测量值3
'measure_value4': str(row[7]) if len(row) > 7 and row[7] else '', # 测量值4
'measure_value5': str(row[8]) if len(row) > 8 and row[8] else '', # 测量值5
'measure_item': str(self.convert_float(row[2])) or '', # 检测项目列
'measure_value1': str(self.convert_float(row[4])) if row[4] else '', # 测量值1
'measure_value2': str(self.convert_float(row[5])) if row[5] else '', # 测量值2
'measure_value3': str(self.convert_float(row[6])) if len(row) > 6 and row[6] else '', # 测量值3
'measure_value4': str(self.convert_float(row[7])) if len(row) > 7 and row[7] else '', # 测量值4
'measure_value5': str(self.convert_float(row[8])) if len(row) > 8 and row[8] else '', # 测量值5
'measure_result': 'NG' if row[9] == 'NG' else 'OK', # 判定列
'remark': row[10] if len(row) > 10 and row[10] else '', # 备注列
'remark': self.convert_float(row[10]) if len(row) > 10 and row[10] else '', # 备注列
}
for i in range(1, 6):
@@ -194,7 +214,7 @@ class ImportComplexModelWizard(models.TransientModel):
self.env['quality.check.measure.line'].create(measure_line_vals)
valid_data_imported = True
quality_check.column_nums = max_columns
# 检查是否有有效数据被导入
@@ -424,7 +444,8 @@ class ImportComplexModelWizard(models.TransientModel):
# )
def download_excel_template(self):
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + '/quality_control/static/src/binary/出厂检验报告上传模版.xlsx'
base_url = self.env['ir.config_parameter'].sudo().get_param(
'web.base.url') + '/quality_control/static/src/binary/出厂检验报告上传模版.xlsx'
# 只有当原始 URL 使用 http 时才替换为 https
if base_url.startswith("http://"):

View File

@@ -6,7 +6,8 @@
<field name="arch" type="xml">
<form>
<group>
<field name="file_data" widget="binary" options="{'accepted_file_extensions': '.xlsx'}"/>
<field name="file_data" widget="binary" filename="filename" options="{'accepted_file_extensions': '.xls,.xlsx'}"/>
<field name="filename" invisible="1"/>
</group>
<footer>
<button string="确认导入" name="import_data" type="object" class="btn-primary"/>

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
class QualityCheckPublishWizard(models.TransientModel):
_name = 'quality.check.publish.wizard'
_description = '质检报告发布确认'
@@ -12,9 +13,10 @@ class QualityCheckPublishWizard(models.TransientModel):
measure_count = fields.Integer('测量件数', readonly=True)
item_count = fields.Integer('检验项目数', readonly=True)
old_report_name = fields.Char('旧出厂检验报告编号', readonly=True)
publish_status = fields.Selection([('draft', '草稿'), ('published', '已发布'), ('canceled', '已撤销')], string='发布状态', readonly=True)
publish_status = fields.Selection([('draft', '草稿'), ('published', '已发布'), ('canceled', '已撤销')], string='发布状态',
readonly=True)
def action_confirm_publish(self):
"""确认发布"""
self.ensure_one()
return self.check_id._do_publish_implementation()
return self.check_id._do_publish_implementation()

View File

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

View File

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

View File

@@ -9,7 +9,6 @@ class Printer(models.Model):
ip_address = fields.Char(string='IP 地址', required=True)
port = fields.Integer(string='端口', default=9100)
class TableStyle(models.Model):
_name = 'table.style'
_description = '标签样式'

View File

@@ -2,7 +2,16 @@
import time, datetime
import hashlib
from odoo import models
from typing import Optional
import socket
import os
import logging
import qrcode
from reportlab.pdfgen import canvas
from reportlab.lib.units import inch
from PyPDF2 import PdfFileReader, PdfFileWriter
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
class Common(models.Model):
_name = 'sf.sync.common'
@@ -92,3 +101,120 @@ class PrintingUtils(models.AbstractModel):
# host = "192.168.50.110" # 可以作为参数传入,或者在此配置
# port = 9100 # 可以作为参数传入,或者在此配置
self.send_to_printer(host, port, zpl_code)
def add_qr_code_to_pdf(self, pdf_path:str, content:str, buttom_text:Optional[str]=False):
"""
在PDF文件中添加二维码
:param pdf_path: PDF文件路径
:param content: 二维码内容
:param buttom_text: 二维码下方文字
:return: 是否成功
"""
if not os.path.exists(pdf_path):
logging.warning(f'PDF文件不存在: {pdf_path}')
return False
# 生成二维码
qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(str(content))
qr.make(fit=True)
qr_img = qr.make_image(fill_color="black", back_color="white")
# 保存二维码为临时文件
qr_temp_path = '/tmp/qr_temp.png'
qr_img.save(qr_temp_path)
# 创建一个临时PDF文件路径
output_temp_path = '/tmp/output_temp.pdf'
try:
# 使用reportlab创建一个新的PDF
# 注册中文字体
font_paths = [
"/usr/share/fonts/windows/simsun.ttc", # Windows系统宋体
"c:/windows/fonts/simsun.ttc", # Windows系统宋体另一个位置
"/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", # Linux Droid字体
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", # 文泉驿正黑
"/usr/share/fonts/chinese/TrueType/simsun.ttc", # 某些Linux发行版位置
]
font_found = False
for font_path in font_paths:
if os.path.exists(font_path):
try:
pdfmetrics.registerFont(TTFont('SimSun', font_path))
font_found = True
break
except:
continue
# 读取原始PDF
with open(pdf_path, "rb") as original_file:
existing_pdf = PdfFileReader(original_file)
output = PdfFileWriter()
# 处理第一页
page = existing_pdf.getPage(0)
# 获取页面尺寸
page_width = float(page.mediaBox.getWidth())
page_height = float(page.mediaBox.getHeight())
# 创建一个新的PDF页面用于放置二维码
c = canvas.Canvas(output_temp_path, pagesize=(page_width, page_height))
# 设置字体
if font_found:
c.setFont('SimSun', 14) # 增大字体大小到14pt
else:
# 如果没有找到中文字体,使用默认字体
c.setFont('Helvetica', 14)
logging.warning("未找到中文字体,将使用默认字体")
# 在右下角绘制二维码,预留边距
qr_size = 1.5 * inch # 二维码大小为2英寸
margin = 0.1 * inch # 边距为0.4英寸
qr_y = margin + 20 # 将二维码向上移动一点,为文字留出空间
c.drawImage(qr_temp_path, page_width - qr_size - margin, qr_y, width=qr_size, height=qr_size)
if buttom_text:
# 在二维码下方绘制文字
text = buttom_text
text_width = c.stringWidth(text, "SimSun" if font_found else "Helvetica", 14) # 准确计算文字宽度
text_x = page_width - qr_size - margin + (qr_size - text_width) / 2 # 文字居中对齐
text_y = margin + 20 # 文字位置靠近底部
c.drawString(text_x, text_y, text)
c.save()
# 读取带有二维码的临时PDF
with open(output_temp_path, "rb") as qr_file:
qr_pdf = PdfFileReader(qr_file)
qr_page = qr_pdf.getPage(0)
# 合并原始页面和二维码页面
page.mergePage(qr_page)
output.addPage(page)
# 添加剩余的页面
for i in range(1, existing_pdf.getNumPages()):
output.addPage(existing_pdf.getPage(i))
# 保存最终的PDF到一个临时文件
final_temp_path = pdf_path + '.tmp'
with open(final_temp_path, "wb") as output_file:
output.write(output_file)
# 替换原始文件
os.replace(final_temp_path, pdf_path)
return True
finally:
# 清理临时文件
if os.path.exists(qr_temp_path):
os.remove(qr_temp_path)
if os.path.exists(output_temp_path):
os.remove(output_temp_path)

View File

@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
import logging
import json
import base64
import logging
from odoo import http
from odoo.http import request
_logger = logging.getLogger(__name__)
class Manufacturing_Connect(http.Controller):

View File

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

View File

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

View File

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

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

@@ -0,0 +1,18 @@
from odoo import models, fields, api
class ApiRequestLog(models.Model):
_name = 'api.request.log'
_description = '接口请求日志'
_order = 'id desc'
name = fields.Char('接口名称')
path = fields.Char('请求路径')
method = fields.Char('请求方法')
request_data = fields.Text('请求数据')
response_data = fields.Text('响应数据')
remote_addr = fields.Char('客户端IP')
response_time = fields.Float('响应时间(秒)', digits=(16, 6))
status = fields.Integer('状态码')
requester = fields.Char('请求方')
responser = fields.Char('响应方')

View File

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

View File

@@ -148,12 +148,17 @@ td.o_required_modifier {
color: #aaa;
}
.o_kanban_primary_left {
display: flex;
flex-direction: row-reverse;
justify-content: flex-start;
// .o_kanban_primary_left {
// display: flex;
// flex-direction: row-reverse;
// justify-content: flex-start;
// }
.o_list_button {
min-width: 32px;
}
.o_list_record_remove {
padding-left: 0px !important;
}
.diameter:before {
content:"Ф";
display:inline;

View File

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

View File

@@ -5,7 +5,7 @@
<record model="ir.ui.view" id="mrs_production_process_parameter_tree">
<field name="model">sf.production.process.parameter</field>
<field name="arch" type="xml">
<tree string="表面工艺可选参数" create="0" delete="0">
<tree string="工艺可选参数" create="0" delete="0">
<field name="code"/>
<field name="name"/>
<field name="gain_way"/>
@@ -15,7 +15,7 @@
<record model="ir.ui.view" id="mrs_production_process_parameter_form">
<field name="model">sf.production.process.parameter</field>
<field name="arch" type="xml">
<form string="表面工艺可选参数" create="0" delete="0" >
<form string="工艺可选参数" create="0" delete="0" >
<sheet>
<div class="oe_title">
<h1>
@@ -104,7 +104,7 @@
<record model="ir.ui.view" id="sf_production_process_category_form">
<field name="model">sf.production.process.category</field>
<field name="arch" type="xml">
<form string="表面工艺类别" create="0" edit="0" delete="1">
<form string="工艺类别" create="0" edit="0" delete="1">
<sheet>
<div class="oe_title">
<h1>
@@ -120,7 +120,7 @@
</group>
</group>
<notebook>
<page string="表面工艺">
<page string="工艺">
<field name='production_process_ids' widget="ony2many">
<tree editable="bottom">
<field name="code" string="编码号" readonly="1" force_save="1"/>
@@ -139,7 +139,7 @@
<record model="ir.ui.view" id="sf_production_process_category_tree">
<field name="model">sf.production.process.category</field>
<field name="arch" type="xml">
<tree string="表面工艺类别" default_order="sequence, id" create="0" edit="0" delete="1">
<tree string="工艺类别" default_order="sequence, id" create="0" edit="0" delete="1">
<field name="sequence" widget="handle" string="序号" readonly="1"/>
<field name="code"/>
<field name="name" string="名称"/>
@@ -163,7 +163,7 @@
<record model="ir.ui.view" id="sf_production_process_tree">
<field name="model">sf.production.process</field>
<field name="arch" type="xml">
<tree string="表面工艺" create="0" edit="0" delete="0">
<tree string="工艺" create="0" edit="0" delete="0">
<field name="sequence" string="加工顺序" readonly="1"/>
<field name="code"/>
<field name="name" string="名称"/>
@@ -175,7 +175,7 @@
<record model="ir.ui.view" id="sf_production_process_form">
<field name="model">sf.production.process</field>
<field name="arch" type="xml">
<form string="表面工艺" create="0" delete="0">
<form string="工艺" create="0" delete="0">
<sheet>
<div class="oe_title">
<h1>
@@ -395,7 +395,7 @@
<field name="view_mode">tree,form</field>
</record>
<record id="sf_production_process" model="ir.actions.act_window">
<field name="name">表面工艺</field>
<field name="name">工艺</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sf.production.process</field>
<field name="view_mode">tree,form</field>
@@ -414,13 +414,13 @@
<!-- </record>-->
<record id="sf_production_process_category" model="ir.actions.act_window">
<field name="name">表面工艺类别</field>
<field name="name">工艺类别</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sf.production.process.category</field>
<field name="view_mode">tree,form</field>
</record>
<record id="mrs_production_process_parameter_action" model="ir.actions.act_window">
<field name="name">表面工艺可选参数</field>
<field name="name">工艺可选参数</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sf.production.process.parameter</field>
<field name="view_mode">tree,form</field>

View File

@@ -78,7 +78,7 @@
<menuitem
id="menu_sf_production_process"
name="表面工艺"
name="工艺"
parent="menu_sf_production_process_1"
sequence="2"
action="sf_production_process"
@@ -86,7 +86,7 @@
<menuitem
id="menu_sf_production_process_category"
name="表面工艺类别"
name="工艺类别"
parent="menu_sf_production_process_1"
sequence="1"
action="sf_production_process_category"
@@ -113,7 +113,7 @@
<menuitem
id="mrs_production_process_parameter_view"
name="表面工艺可选参数"
name="工艺可选参数"
parent="menu_sf_production_process_1"
sequence="2"
action="mrs_production_process_parameter_action"

View File

@@ -14,10 +14,12 @@
<field name="name">原材料</field>
<field name="type">原材料</field>
</record>
<record id="product_category_surface_technics_sf" model="product.category">
<field name="name">表面工艺</field>
<field name="type">表面工艺</field>
<field name="parent_id" ref="sf_manufacturing.product_category_outsource_process"/>
<field name="property_cost_method">fifo</field>
<field name="property_valuation">manual_periodic</field>
</record>
<record id="product_category_cutting_tool_sf" model="product.category">
@@ -40,10 +42,10 @@
<!-- <field name="company_id" ref="base.main_company"/>-->
</record>
<!-- <record id="res_users_bfm" model="res.users">-->
<!-- <field name="name">业务平台</field>-->
<!--&lt;!&ndash; <field name="partner_id" ref="res_partner_bfm"/>&ndash;&gt;-->
<!-- </record>-->
<!-- <record id="res_users_bfm" model="res.users">-->
<!-- <field name="name">业务平台</field>-->
<!--&lt;!&ndash; <field name="partner_id" ref="res_partner_bfm"/>&ndash;&gt;-->
<!-- </record>-->
<record id="product_functional_tool_sf" model="product.product">
<field name="name">功能刀具</field>

View File

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

View File

@@ -9,8 +9,9 @@
""",
'category': 'sf',
'website': 'https://www.sf.jikimo.com',
'depends': ['sf_sale', 'sf_dlm', 'sf_manufacturing','jikimo_attachment_viewer'],
'depends': ['sf_sale', 'sf_dlm', 'sf_manufacturing', 'jikimo_attachment_viewer'],
'data': [
'data/sequence.xml',
'data/stock_data.xml',
'views/product_template_management_view.xml',
],

View File

@@ -0,0 +1,10 @@
<odoo>
<data>
<record id="sequence_production_process_parameter" model="ir.sequence">
<field name="name">工艺可选参数编码序列</field>
<field name="code">sf.production.process.parameter</field>
<field name="padding">3</field>
<field name="number_increment">1</field>
</record>
</data>
</odoo>

View File

@@ -1,2 +1,4 @@
# from . import product_template
# from . import product_supplierinfo
from . import sf_production_common
from . import mrp_routing_workcenter

View File

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

View File

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

View File

@@ -39,9 +39,9 @@
attrs="{'invisible': [('categ_type', 'not in', ['成品','坯料', '原材料'])],'readonly': [('id', '!=', False)]}"/>
<field name="materials_type_id" string="型号" placeholder="请选择" options="{'no_create': True}"
attrs="{'invisible': [('categ_type', 'not in', ['成品','坯料', '原材料'])],'readonly': [('id', '!=', False)]}"/>
<field name="server_product_process_parameters_id" string="表面工艺参数"
<field name="server_product_process_parameters_id" string="工艺参数"
options="{'no_create': True}"
attrs="{'invisible': ['|',('categ_type', '!=', '表面工艺'),('categ_type', '=', False)]}"/>
attrs="{'invisible': ['|',('detailed_type', '!=', 'service'),('detailed_type', '=', False)]}"/>
<field name="cutting_tool_material_id" class="custom_required"
options="{'no_create': True}"
attrs="{'invisible': [('categ_type', '!=', '刀具')],'required': [('categ_type', '=', '刀具')],'readonly': [('id', '!=', False)]}"

View File

@@ -5,7 +5,7 @@ import json
import base64
import logging
import psycopg2
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from odoo import http, fields
from odoo.http import request
@@ -414,7 +414,7 @@ class Sf_Dashboard_Connect(http.Controller):
# 工单计划量切换为CNC工单
plan_data_total_counts = work_order_obj.search_count(
[('production_id.production_line_id.name', '=', line),
[('production_line_id.name', '=', line), ('id', '!=', 8061),
('state', 'in', ['ready', 'progress', 'done']), ('routing_type', '=', 'CNC加工')])
# # 工单完成量
@@ -423,13 +423,13 @@ class Sf_Dashboard_Connect(http.Controller):
# 工单完成量切换为CNC工单
plan_data_finish_counts = work_order_obj.search_count(
[('production_id.production_line_id.name', '=', line),
[('production_line_id.name', '=', line),
('state', 'in', ['done']), ('routing_type', '=', 'CNC加工')])
# 超期完成量
# 搜索所有已经完成的工单
plan_data_overtime = work_order_obj.search([
('production_id.production_line_id.name', '=', line),
('production_line_id.name', '=', line),
('state', 'in', ['done']),
('routing_type', '=', 'CNC加工')
])
@@ -448,9 +448,14 @@ class Sf_Dashboard_Connect(http.Controller):
])
# 过滤出那些检测结果状态为 '返工' 或 '报废' 的记录
faulty_plans = plan_data.filtered(lambda p: any(
result.test_results in ['返工', '报废'] for result in p.production_id.detection_result_ids
))
# faulty_plans = plan_data.filtered(lambda p: any(
# result.test_results in ['返工', '报废'] for result in p.production_id.detection_result_ids
# ))
faulty_plans = request.env['quality.check'].sudo().search([
('operation_id.name', '=', 'CNC加工'),
('quality_state', 'in', ['fail'])
])
# 查找制造订单取消与归档的数量
cancel_order_count = production_obj.search_count(
@@ -567,7 +572,7 @@ class Sf_Dashboard_Connect(http.Controller):
"""
res = {'status': 1, 'message': '成功', 'data': {}}
# plan_obj = request.env['sf.production.plan'].sudo()
plan_obj = request.env['mrp.workorder'].sudo().search([('routing_type', '=', 'CNC加工')])
# plan_obj = request.env['mrp.workorder'].sudo().search([('routing_type', '=', 'CNC加工')])
line_list = ast.literal_eval(kw['line_list'])
begin_time_str = kw['begin_time'].strip('"')
end_time_str = kw['end_time'].strip('"')
@@ -617,11 +622,19 @@ class Sf_Dashboard_Connect(http.Controller):
for time_interval in time_intervals:
start_time, end_time = time_interval
orders = plan_obj.search([
('production_id.production_line_id.name', '=', line),
# orders = plan_obj.search([
# ('production_line_id.name', '=', line),
# ('state', 'in', ['done']),
# (date_field_name, '>=', start_time.strftime('%Y-%m-%d %H:%M:%S')),
# (date_field_name, '<=', end_time.strftime('%Y-%m-%d %H:%M:%S')) # 包括结束时间
# ])
orders = request.env['mrp.workorder'].sudo().search([
('routing_type', '=', 'CNC加工'), # 将第一个条件合并进来
('production_line_id.name', '=', line),
('state', 'in', ['done']),
(date_field_name, '>=', start_time.strftime('%Y-%m-%d %H:%M:%S')),
(date_field_name, '<=', end_time.strftime('%Y-%m-%d %H:%M:%S')) # 包括结束时间
(date_field_name, '<=', end_time.strftime('%Y-%m-%d %H:%M:%S'))
])
# 使用小时和分钟作为键,确保每个小时的数据有独立的键
@@ -638,18 +651,22 @@ class Sf_Dashboard_Connect(http.Controller):
for date in date_list:
next_day = date + timedelta(days=1)
orders = plan_obj.search([('production_id.production_line_id.name', '=', line), ('state', 'in', ['done']),
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
])
rework_orders = plan_obj.search(
[('production_id.production_line_id.name', '=', line), ('state', 'in', ['rework']),
orders = request.env['mrp.workorder'].sudo().search(
[('production_id.production_line_id.name', '=', line), ('state', 'in', ['done']),
('routing_type', '=', 'CNC加工'),
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
])
not_passed_orders = plan_obj.search(
rework_orders = request.env['mrp.workorder'].sudo().search(
[('production_id.production_line_id.name', '=', line), ('state', 'in', ['rework']),
('routing_type', '=', 'CNC加工'),
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
])
not_passed_orders = request.env['mrp.workorder'].sudo().search(
[('production_id.production_line_id.name', '=', line), ('state', 'in', ['scrap', 'cancel']),
('routing_type', '=', 'CNC加工'),
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
])
@@ -751,11 +768,14 @@ 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']),
('production_id.state', 'not in', ['cancel', 'done']), ('active', '=', True)
# not_done_orders = plan_obj.search(
# [('production_line_id.name', '=', line), ('state', 'not in', ['finished']),
# ('production_id.state', 'not in', ['cancel', 'done']), ('active', '=', True)
# ])
not_done_orders = request.env['mrp.workorder'].sudo().search(
[('production_line_id.name', '=', line), ('state', 'in', ['ready', 'progress']),
('routing_type', '=', 'CNC加工')
])
# print(not_done_orders)
# 完成订单
# 获取当前时间并计算24小时前的时间
@@ -807,16 +827,18 @@ class Sf_Dashboard_Connect(http.Controller):
'draft': '待排程',
'done': '已排程',
'processing': '生产中',
'finished': '已完成'
'finished': '已完成',
'ready': '待加工',
'progress': '生产中',
}
line_dict = {
'sequence': id_to_sequence[order.id],
'workorder_name': order.name,
'workorder_name': order.production_id.name,
'blank_name': blank_name,
'material': material,
'dimensions': dimensions,
'order_qty': order.product_qty,
'order_qty': 1,
'state': state_dict[order.state],
}
@@ -897,15 +919,17 @@ class Sf_Dashboard_Connect(http.Controller):
cur.execute(sql2, (item,))
result2 = cur.fetchall()
# print('result2========', result2)
#
for row in result:
res['data'][item] = {'idle_count': row[0]}
alarm_count = []
for row in result2:
alarm_count.append(row[1])
if row[0]:
total_alarm_time += abs(float(row[0]))
if float(row[0]) >= 28800:
continue
# total_alarm_time += abs(float(row[0]))
total_alarm_time += float(row[0])
else:
total_alarm_time += 0.0
if len(list(set(alarm_count))) == 1:
@@ -915,6 +939,7 @@ class Sf_Dashboard_Connect(http.Controller):
alarm_count_num = 1
else:
alarm_count_num = len(list(set(alarm_count)))
res['data'][item]['total_alarm_time'] = total_alarm_time / 3600
res['data'][item]['alarm_count_num'] = alarm_count_num
@@ -1097,169 +1122,169 @@ class Sf_Dashboard_Connect(http.Controller):
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 >= CURRENT_DATE -- 今日 00:00:00
AND time < CURRENT_DATE + 1 -- 明日 00:00:00
AND device_state in ('待机', '警告', '运行中')
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 >= CURRENT_DATE -- 今日 00:00:00
AND time < CURRENT_DATE + 1 -- 明日 00:00:00
AND device_state in ('待机', '警告', '运行中')
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 time >= DATE_TRUNC('MONTH', CURRENT_DATE)
AND time < DATE_TRUNC('MONTH', CURRENT_DATE) + INTERVAL '1 MONTH'
AND device_state in ('待机', '警告', '运行中')
ORDER BY time ASC
LIMIT 1;
""", (item,))
first_month = fetch_result_as_dict(cur)
# print("当月第一条记录:", first_month)
# 获取当月最新一条记录
with conn.cursor() as cur:
cur.execute("""
SELECT * FROM device_data
WHERE device_name = %s
AND time >= DATE_TRUNC('MONTH', CURRENT_DATE)
AND time < DATE_TRUNC('MONTH', CURRENT_DATE) + INTERVAL '1 MONTH'
AND device_state in ('待机', '警告', '运行中')
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 in ('待机', '警告', '运行中')
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 in ('待机', '警告', '运行中')
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/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 >= CURRENT_DATE -- 今日 00:00:00
# AND time < CURRENT_DATE + 1 -- 明日 00:00:00
# AND device_state in ('待机', '警告', '运行中')
# 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 >= CURRENT_DATE -- 今日 00:00:00
# AND time < CURRENT_DATE + 1 -- 明日 00:00:00
# AND device_state in ('待机', '警告', '运行中')
# 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 time >= DATE_TRUNC('MONTH', CURRENT_DATE)
# AND time < DATE_TRUNC('MONTH', CURRENT_DATE) + INTERVAL '1 MONTH'
# AND device_state in ('待机', '警告', '运行中')
# ORDER BY time ASC
# LIMIT 1;
# """, (item,))
# first_month = fetch_result_as_dict(cur)
# # print("当月第一条记录:", first_month)
#
# # 获取当月最新一条记录
# with conn.cursor() as cur:
# cur.execute("""
# SELECT * FROM device_data
# WHERE device_name = %s
# AND time >= DATE_TRUNC('MONTH', CURRENT_DATE)
# AND time < DATE_TRUNC('MONTH', CURRENT_DATE) + INTERVAL '1 MONTH'
# AND device_state in ('待机', '警告', '运行中')
# 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 in ('待机', '警告', '运行中')
# 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 in ('待机', '警告', '运行中')
# 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="*")
@@ -1273,10 +1298,7 @@ class Sf_Dashboard_Connect(http.Controller):
conn = psycopg2.connect(**db_config)
# 获取请求的机床数据
machine_list = ast.literal_eval(kw['machine_list'])
time_threshold = datetime.now() - timedelta(days=1)
alarm_last_24_time = 0.0
alarm_all_time = 0.0
time_threshold = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
def fetch_result_as_dict(cursor):
"""辅助函数:将查询结果转为字典"""
@@ -1286,6 +1308,9 @@ class Sf_Dashboard_Connect(http.Controller):
# 获取当前时间的时间戳
current_timestamp = datetime.now().timestamp()
for item in machine_list:
alarm_last_24_time = 0.0
alarm_all_time = 0.0
euipment_obj = request.env['maintenance.equipment'].sudo().search([('code', '=', item)])
# 机床上线时间段
first_online_duration = current_timestamp - euipment_obj.first_online_time.timestamp()
@@ -1302,8 +1327,8 @@ class Sf_Dashboard_Connect(http.Controller):
cur.execute("""
SELECT * FROM device_data
WHERE device_name = %s
AND device_state in ('待机', '警告', '运行中') AND time >= %s AND process_time IS NOT NULL
ORDER BY time ASC
AND device_state in ('待机', '警告', '运行中') AND time <= %s AND process_time IS NOT NULL
ORDER BY time DESC
LIMIT 1;
""", (item, time_threshold))
last_24_time = fetch_result_as_dict(cur)
@@ -1323,20 +1348,21 @@ class Sf_Dashboard_Connect(http.Controller):
alarm_last_24_nums = []
with conn.cursor() as cur:
cur.execute("""
SELECT DISTINCT ON (alarm_start_time) alarm_time, alarm_start_time
SELECT DISTINCT ON (alarm_start_time, alarm_time) alarm_time, alarm_start_time
FROM device_data
WHERE device_name = %s
AND alarm_start_time IS NOT NULL AND time >= %s;
AND alarm_start_time IS NOT NULL AND alarm_start_time::timestamp >= %s;
""", (item, time_threshold))
results = cur.fetchall()
logging.info("results============:%s" % results)
for result in results:
alarm_last_24_nums.append(result[1])
if result[0]:
if float(result[0]) >= 1000:
if float(result[0]) >= 28800:
continue
alarm_last_24_time += float(result[0])
alarm_last_24_time = float(result[0])
else:
alarm_last_24_time += 0.0
alarm_last_24_time = 0.0
alarm_all_nums = []
with conn.cursor() as cur:
@@ -1350,7 +1376,7 @@ class Sf_Dashboard_Connect(http.Controller):
for result in results:
alarm_all_nums.append(result[1])
if result[0]:
if float(result[0]) >= 1000:
if float(result[0]) >= 28800:
continue
alarm_all_time += float(result[0])
else:
@@ -1385,3 +1411,384 @@ class Sf_Dashboard_Connect(http.Controller):
conn.close()
return json.dumps(res)
# @http.route('/api/utilization/rate', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
# def UtilizationRate(self, **kw):
# """
# 获取稼动率
# """
# logging.info("kw=:%s" % kw)
# res = {'status': 1, 'message': '成功', 'data': {}}
# # 获取请求的机床数据
# machine_list = ast.literal_eval(kw['machine_list'])
# line = kw['line']
# orders = request.env['mrp.workorder'].sudo().search([
# ('routing_type', '=', 'CNC加工'), # 将第一个条件合并进来
# ('production_line_id.name', '=', line),
# ('state', 'in', ['done'])
# ])
#
# faulty_plans = request.env['quality.check'].sudo().search([
# ('operation_id.name', '=', 'CNC加工'),
# ('quality_state', 'in', ['fail'])
# ])
#
# # 计算时间范围
# now = datetime.now()
# today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
# month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
#
# total_power_on_time = 0
# month_power_on_time = 0
# today_power_on_time = 0
# today_power_on_dict = {}
# today_alarm_dict = {}
# single_machine_dict = {}
#
# today_order_data = []
# month_order_data = []
# today_check_ng = []
# month_check_ng = []
#
# total_alarm_time = 0
# today_alarm_time = 0
# month_alarm_time = 0
#
# for order in orders:
# time = order.date_finished
# if time >= today_start:
# today_order_data.append(order)
# if time >= month_start:
# month_order_data.append(order)
#
# for faulty_plan in faulty_plans:
# time = faulty_plan.write_date
# if time >= today_start:
# today_check_ng.append(faulty_plan)
# if time >= month_start:
# month_check_ng.append(faulty_plan)
#
# # 连接数据库
# conn = psycopg2.connect(**db_config)
# for item in machine_list:
# with conn.cursor() as cur:
# cur.execute("""
# (
# SELECT power_on_time, 'latest' AS record_type
# FROM device_data
# WHERE device_name = %s
# AND power_on_time IS NOT NULL
# ORDER BY time DESC
# LIMIT 1
# )
# UNION ALL
# (
# SELECT power_on_time, 'month_first' AS record_type
# FROM device_data
# WHERE device_name = %s
# AND power_on_time IS NOT NULL
# AND time >= date_trunc('month', CURRENT_DATE) -- ✅ 修复日期函数
# AND time < (date_trunc('month', CURRENT_DATE) + INTERVAL '1 month')::date
# ORDER BY time ASC
# LIMIT 1
# )
# UNION ALL
# (
# SELECT power_on_time, 'day_first' AS record_type
# FROM device_data
# WHERE device_name = %s
# AND power_on_time IS NOT NULL
# AND time::date = CURRENT_DATE -- ✅ 更高效的写法
# ORDER BY time ASC
# LIMIT 1
# );
# """, (item, item, item))
# results = cur.fetchall()
# if len(results) >= 1:
# total_power_on_time += convert_to_seconds(results[0][0])
# else:
# total_power_on_time += 0
# if len(results) >= 2:
# month_power_on_time += convert_to_seconds(results[1][0])
# else:
# month_power_on_time += 0
# if len(results) >= 3:
# today_power_on_time += convert_to_seconds(results[2][0])
# today_power_on_dict[item] = today_power_on_time
# else:
# today_power_on_time += 0
# today_power_on_dict[item] = 0
#
# with conn.cursor() as cur:
# cur.execute("""
# SELECT DISTINCT ON (alarm_start_time) alarm_time, alarm_start_time
# FROM device_data
# WHERE device_name = %s AND alarm_start_time IS NOT NULL
# ORDER BY alarm_start_time, time;
# """, (item,))
# results = cur.fetchall()
# today_data = []
# month_data = []
#
# for record in results:
# if record[0]:
# if float(record[0]) >= 28800:
# continue
# total_alarm_time += float(record[0])
# else:
# total_alarm_time += 0.0
# alarm_start = datetime.strptime(record[1], "%Y-%m-%d %H:%M:%S")
# if alarm_start >= today_start:
# today_data.append(record)
# if alarm_start >= month_start:
# month_data.append(record)
# if today_data:
# for today in today_data:
# if today[0]:
# if float(today[0]) >= 28800:
# continue
# today_alarm_time += float(today[0])
# today_alarm_dict[item] = today_alarm_time
# else:
# today_alarm_time += 0.0
# today_alarm_dict[item] = 0
# else:
# today_alarm_dict[item] = 0
# for month in month_data:
# if month[0]:
# if float(month[0]) >= 28800:
# continue
# month_alarm_time += float(month[0])
# else:
# month_alarm_time += 0.0
#
# conn.close()
#
# logging.info('报警时间总月日=============%s, %s, %s' % (total_alarm_time, month_alarm_time, today_alarm_time))
# # 计算时间开动率(累计、月、日)
# if total_power_on_time:
# total_power_on_rate = (total_power_on_time - total_alarm_time) / total_power_on_time
# else:
# total_power_on_rate = 0
# if month_power_on_time:
# month_power_on_rate = (total_power_on_time - month_power_on_time - month_alarm_time) / (
# total_power_on_time - month_power_on_time)
# else:
# month_power_on_rate = 0
# if today_power_on_time:
# today_power_on_rate = (total_power_on_time - today_power_on_time - today_alarm_time) / (
# total_power_on_time - today_power_on_time)
# else:
# today_power_on_rate = 0
# logging.info("总开动率: %s" % total_power_on_rate)
# logging.info("月开动率: %s" % month_power_on_rate)
# logging.info("日开动率: %s" % today_power_on_rate)
#
# # 计算性能开动率(累计、月、日)
# logging.info('完成工单数量: %s' % len(orders))
# total_performance_rate = len(orders) * 30 * 60 / (total_power_on_time - total_alarm_time)
# month_performance_rate = len(month_data) * 30 * 60 / (
# total_power_on_time - month_power_on_time - month_alarm_time)
# today_performance_rate = len(today_data) * 30 * 60 / (
# total_power_on_time - today_power_on_time - today_alarm_time) if today_power_on_time != 0 else 0
# logging.info("总性能率: %s" % total_performance_rate)
# logging.info("月性能率: %s" % month_performance_rate)
# logging.info("日性能率: %s" % today_performance_rate)
#
# # 计算累计合格率
# total_pass_rate = (len(orders) - len(today_check_ng)) / len(orders) if len(orders) != 0 else 0
# month_pass_rate = (len(month_order_data) - len(month_check_ng)) / len(month_order_data) if len(month_order_data) != 0 else 0
# today_pass_rate = (len(today_order_data) - len(today_check_ng)) / len(today_order_data) if len(today_order_data) != 0 else 0
# logging.info("总合格率: %s" % total_pass_rate)
# logging.info("月合格率: %s" % month_pass_rate)
# logging.info("日合格率: %s" % today_pass_rate)
#
# # # 返回数据
# # res['data'][item] = {
# # 'total_utilization_rate': total_power_on_rate * total_performance_rate * total_pass_rate,
# # 'month_utilization_rate': month_power_on_rate * month_performance_rate * month_pass_rate,
# # 'today_utilization_rate': today_power_on_rate * today_performance_rate * today_pass_rate,
# # }
# for i in machine_list:
# single_machine_utilization_rate = total_power_on_time - today_power_on_dict[i] - today_alarm_dict[i] / (
# total_power_on_time - today_power_on_dict[i])
# single_machine_dict[i] = single_machine_utilization_rate * today_performance_rate * today_pass_rate
#
# res['data'] = {
# 'total_utilization_rate': total_power_on_rate * total_performance_rate * total_pass_rate,
# 'month_utilization_rate': month_power_on_rate * month_performance_rate * month_pass_rate,
# 'today_utilization_rate': today_power_on_rate * today_performance_rate * today_pass_rate,
# 'single_machine_dict': single_machine_dict
# }
#
# return json.dumps(res)
@http.route('/api/RunningTime', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
def UtilizationRate(self, **kw):
"""
获取稼动率
"""
logging.info("kw=:%s" % kw)
res = {'status': 1, 'message': '成功', 'data': {}}
# 获取请求的机床数据
machine_list = ast.literal_eval(kw['machine_list'])
faulty_plans = request.env['quality.check'].sudo().search([
('operation_id.name', '=', 'CNC加工'),
('quality_state', 'in', ['fail'])
])
# 计算时间范围
now = datetime.now()
today_start = now.replace(hour=0, minute=0, second=0, microsecond=0)
month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
total_power_on_time = 0
month_power_on_time = 0
today_power_on_time = 0
today_power_on_dict = {}
today_alarm_dict = {}
single_machine_dict = {}
today_order_data = []
month_order_data = []
today_check_ng = []
month_check_ng = []
total_alarm_time = 0
today_alarm_time = 0
month_alarm_time = 0
# 连接数据库
conn = psycopg2.connect(**db_config)
for item in machine_list:
with conn.cursor() as cur:
cur.execute("""
(
SELECT power_on_time, 'latest' AS record_type
FROM device_data
WHERE device_name = %s
AND power_on_time IS NOT NULL
ORDER BY time DESC
LIMIT 1
)
UNION ALL
(
SELECT power_on_time, 'month_first' AS record_type
FROM device_data
WHERE device_name = %s
AND power_on_time IS NOT NULL
AND time >= date_trunc('month', CURRENT_DATE) -- ✅ 修复日期函数
AND time < (date_trunc('month', CURRENT_DATE) + INTERVAL '1 month')::date
ORDER BY time ASC
LIMIT 1
)
UNION ALL
(
SELECT power_on_time, 'day_first' AS record_type
FROM device_data
WHERE device_name = %s
AND power_on_time IS NOT NULL
AND time >= CURRENT_DATE
AND time < CURRENT_DATE + INTERVAL '1 day'
ORDER BY time ASC
LIMIT 1
);
""", (item, item, item))
results = cur.fetchall()
logging.info('====================%s' % results)
if len(results) >= 1:
total_power_on_time += convert_to_seconds(results[0][0])
else:
total_power_on_time += 0
if len(results) >= 2:
month_power_on_time += convert_to_seconds(results[1][0])
else:
month_power_on_time += 0
if len(results) >= 3:
today_power_on_time += convert_to_seconds(results[2][0])
# today_power_on_dict[item] = today_power_on_time
else:
today_power_on_time += convert_to_seconds(results[0][0])
# today_power_on_dict[item] = 0
if results[0][0] == '0H0M':
logging.info('=woshide=%s' % results[0][0])
logging.info(type(results[0][0]))
total_power_on_time += convert_to_seconds(results[1][0])
today_power_on_time += convert_to_seconds(results[1][0])
with conn.cursor() as cur:
cur.execute("""
SELECT DISTINCT ON (alarm_start_time) alarm_time, alarm_start_time
FROM device_data
WHERE device_name = %s AND alarm_start_time IS NOT NULL
ORDER BY alarm_start_time, time;
""", (item,))
results = cur.fetchall()
today_data = []
month_data = []
for record in results:
if record[0]:
if float(record[0]) >= 86400:
continue
total_alarm_time += abs(float(record[0]))
else:
total_alarm_time += 0.0
alarm_start = datetime.strptime(record[1], "%Y-%m-%d %H:%M:%S")
if alarm_start >= today_start:
today_data.append(record)
if alarm_start >= month_start:
month_data.append(record)
if today_data:
for today in today_data:
if today[0]:
if float(today[0]) >= 28800:
continue
today_alarm_time += abs(float(today[0]))
today_alarm_dict[item] = today_alarm_time
else:
today_alarm_time += 0.0
today_alarm_dict[item] = 0
else:
today_alarm_dict[item] = 0
for month in month_data:
if month[0]:
if float(month[0]) >= 28800:
continue
month_alarm_time += abs(float(month[0]))
else:
month_alarm_time += 0.0
conn.close()
logging.info('在线时间总月日=============%s, %s, %s' % (total_power_on_time, month_power_on_time, today_power_on_time))
logging.info('报警时间总月日=============%s, %s, %s' % (total_alarm_time, month_alarm_time, today_alarm_time))
# 计算时间开动率(累计、月、日)
if total_power_on_time:
total_power_on_rate = (total_power_on_time - total_alarm_time) / total_power_on_time
else:
total_power_on_rate = 0
if month_power_on_time:
month_power_on_rate = (total_power_on_time - month_power_on_time - month_alarm_time) / (
total_power_on_time - month_power_on_time)
else:
month_power_on_rate = 0
if today_power_on_time:
today_power_on_rate = (total_power_on_time - today_power_on_time - today_alarm_time) / (
total_power_on_time - today_power_on_time)
else:
today_power_on_rate = 0
logging.info("总开动率: %s" % total_power_on_rate)
logging.info("月开动率: %s" % month_power_on_rate)
logging.info("日开动率: %s" % today_power_on_rate)
res['data'] = {
'all_time_work_rate': total_power_on_rate,
'month_work_rate': month_power_on_rate,
'day_work_rate': today_power_on_rate,
}
return json.dumps(res)

View File

@@ -1,15 +1,21 @@
# -*- coding: utf-8 -*-
import logging
from ftplib import FTP
import os
import re
from ftplib import FTP, error_perm
_logger = logging.getLogger(__name__)
class FTP_P(FTP):
"""
重写FTP类重写dirs方法
重写FTP类重写dirs方法,增加编码处理
"""
def __init__(self, host='', user='', passwd='', acct='', timeout=None, encoding='gbk'):
"""初始化时指定编码方式"""
super().__init__(host, user, passwd, acct, timeout)
self.encoding = encoding
def dirs(self, *args):
"""List a directory in long form.
By default list current directory to stdout.
@@ -30,7 +36,50 @@ class FTP_P(FTP):
tempdic['name'] = [file for file in r_files if file != "." and file != ".."]
# 去除. ..
return tempdic
# return [file for file in r_files if file != "." and file != ".."]
def nlst(self, *args):
"""Get a list of files in a directory."""
files = []
def append(line):
try:
if isinstance(line, bytes):
files.append(line.decode(self.encoding))
else:
files.append(line)
except UnicodeDecodeError:
files.append(line.decode('utf-8', errors='replace'))
cmd = 'NLST'
if args:
cmd = cmd + ' ' + args[0]
self.retrlines(cmd, append)
return files
def cwd(self, dirname):
"""Change to a directory."""
try:
if isinstance(dirname, bytes):
dirname = dirname.decode(self.encoding)
return super().cwd(dirname)
except UnicodeEncodeError:
return super().cwd(dirname.encode(self.encoding).decode('utf-8'))
def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
"""Store a file in binary mode."""
try:
if isinstance(cmd, bytes):
cmd = cmd.decode(self.encoding)
return super().storbinary(cmd, fp, blocksize, callback, rest)
except UnicodeEncodeError:
return super().storbinary(cmd.encode(self.encoding).decode('utf-8'), fp, blocksize, callback, rest)
def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
"""Retrieve a file in binary mode."""
try:
if isinstance(cmd, bytes):
cmd = cmd.decode(self.encoding)
return super().retrbinary(cmd, callback, blocksize, rest)
except UnicodeEncodeError:
return super().retrbinary(cmd.encode(self.encoding).decode('utf-8'), callback, blocksize, rest)
# FTP接口类
@@ -52,8 +101,8 @@ class FtpController:
print(self.username, self.port, self.host, self.password)
ftp = FTP_P()
_logger.info("===================connect==================")
# self.ftp.set_debuglevel(2) #打开调试级别2显示详细信息
ftp.set_pasv(0) # 0主动模式 1 #被动模式
# ftp.set_debuglevel(2) #打开调试级别2显示详细信息
# ftp.set_pasv(1) # 0主动模式 1 #被动模式
try:
ftp.connect(self.host, self.port)
ftp.login(self.username, self.password)
@@ -128,3 +177,145 @@ class FtpController:
:return:
"""
self.ftp.delete(delpath)
def transfer_files(
source_ftp_info,
target_ftp_info,
source_dir,
target_dir,
end_with=None,
match_str=None,
keep_dir=False):
"""
从源FTP服务器下载所有{end_with}结尾的文件并上传到目标FTP服务器,保持目录结构
Args:
source_ftp_info: dict, 源FTP连接信息 {host, port, username, password}
target_ftp_info: dict, 目标FTP连接信息 {host, port, username, password}
source_dir: str, 源FTP上的起始目录
target_dir: str, 目标FTP上的目标目录
keep_dir: bool, 是否保持目录结构,默认False
"""
transfered_file_list = []
try:
# 连接源FTP
source_ftp = FtpController(
source_ftp_info['host'],
source_ftp_info['port'],
source_ftp_info['username'],
source_ftp_info['password']
)
if not source_ftp.ftp:
raise Exception("编程文件FTP连接失败")
source_ftp.ftp.set_pasv(1)
# 连接目标FTP
target_ftp = FtpController(
target_ftp_info['host'],
target_ftp_info['port'],
target_ftp_info['username'],
target_ftp_info['password']
)
if not source_ftp.ftp:
raise Exception("机床FTP连接失败")
source_ftp.ftp.set_pasv(1)
# 递归遍历源目录
def traverse_dir(current_dir, relative_path=''):
source_ftp.ftp.cwd(current_dir)
file_list = source_ftp.ftp.nlst()
for item in file_list:
try:
# 尝试进入目录
source_ftp.ftp.cwd(f"{current_dir}/{item}")
# 如果成功则是目录
new_relative_path = os.path.join(relative_path, item)
# 在目标FTP创建对应目录
try:
if keep_dir:
target_ftp.ftp.mkd(f"{target_dir}/{new_relative_path}")
except:
pass # 目录可能已存在
# 递归遍历子目录
traverse_dir(f"{current_dir}/{item}", new_relative_path)
source_ftp.ftp.cwd('..')
except:
matched = False
# 文件名匹配字符串BT30-(两个字符)-all.nc, 例6667_20250422-BT30-ZM-all.nc
if match_str and re.match(match_str, item):
matched = True
elif end_with and item.lower().endswith(end_with):
matched = True
if matched:
# 下载到临时文件
temp_path = f"/tmp/{item}"
with open(temp_path, 'wb') as f:
source_ftp.ftp.retrbinary(f'RETR {item}', f.write)
# 上传到目标FTP对应目录
if keep_dir:
target_path = f"{target_dir}/{relative_path}/{item}"
else:
target_path = f"{target_dir}/{item}"
with open(temp_path, 'rb') as f:
target_ftp.ftp.storbinary(f'STOR {target_path}', f)
transfered_file_list.append(item)
# 删除临时文件
os.remove(temp_path)
logging.info(f"已传输文件: {item}")
# 清空目标目录下的所有内容
try:
target_ftp.ftp.cwd(target_dir)
files = target_ftp.ftp.nlst()
for f in files:
try:
# 尝试删除文件
target_ftp.ftp.delete(f)
except:
try:
# 如果删除失败,可能是目录,递归删除目录
def remove_dir(path):
target_ftp.ftp.cwd(path)
sub_files = target_ftp.ftp.nlst()
for sf in sub_files:
try:
target_ftp.ftp.delete(sf)
except:
remove_dir(f"{path}/{sf}")
target_ftp.ftp.cwd('..')
target_ftp.ftp.rmd(path)
remove_dir(f"{target_dir}/{f}")
except:
logging.error(f"无法删除 {f}")
pass
logging.info(f"已清空目标目录 {target_dir}")
except Exception as e:
logging.error(f"清空目标目录失败: {str(e)}")
raise Exception(f"清空目标目录失败: {str(e)}")
# 开始遍历
traverse_dir(source_dir)
logging.info("所有文件传输完成")
return transfered_file_list
except Exception as e:
logging.error(f"传输过程出错: {str(e)}")
raise e
finally:
# 关闭FTP连接
try:
source_ftp.ftp.quit()
target_ftp.ftp.quit()
except:
pass

View File

@@ -1,26 +1,5 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_delivery_record_form_inherit_sf" model="ir.ui.view">
<field name="name">delivery.record.form.inherit.sf</field>
<field name="model">mrp.workorder</field>
<field name="inherit_id" ref="mrp.mrp_production_workorder_form_view_inherit"/>
<field name="arch" type="xml">
<xpath expr="//page[last()-3]" position="before">
<!-- <page string="下发记录" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'>-->
<page string="下发记录" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "HDR")]}'>
<field name="delivery_records">
<tree create="false">
<field name="delivery_type"/>
<field name="delivery_time"/>
<field name="influence_record"/>
</tree>
</field>
</page>
</xpath>
</field>
</record>
<!-- <record id="seqence_b_purchase_order" model='ir.sequence'> -->
<!-- <field name='name'>Purchase Order</field> -->
<!-- <field name='code'>sf_machine_connect.delivery.record</field> -->

View File

@@ -3,9 +3,9 @@
<record id="view_machine_info_form_inherit_sf" model="ir.ui.view">
<field name="name">machine.info.form.inherit.sf</field>
<field name="model">mrp.workorder</field>
<field name="inherit_id" ref="mrp.mrp_production_workorder_form_view_inherit"/>
<field name="inherit_id" ref="sf_manufacturing.view_mrp_production_workorder_tray_form_inherit_sf"/>
<field name="arch" type="xml">
<xpath expr="//page[last()-3]" position="before">
<xpath expr="//page[@name='CMR']" position="after">
<page string="机床信息" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "MTI")]}'>
<group string="机床信息">
<group>
@@ -33,6 +33,15 @@
</group>
</group>
</page>
<page string="下发记录" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "HDR")]}'>
<field name="delivery_records">
<tree create="false">
<field name="delivery_type"/>
<field name="delivery_time"/>
<field name="influence_record"/>
</tree>
</field>
</page>
</xpath>
</field>
</record>

View File

@@ -2,6 +2,8 @@
import json
import base64
import logging
import qrcode
from io import BytesIO
from datetime import timedelta
import requests
from odoo.addons.sf_base.commons.common import Common
@@ -826,6 +828,34 @@ class SfMaintenanceEquipment(models.Model):
image_lq_id = fields.Many2many('maintenance.equipment.image', 'equipment_lq_id', string='冷却方式',
domain="[('type', '=', '冷却方式')]")
ftp_host = fields.Char('FTP 主机')
ftp_port = fields.Char('FTP 端口')
ftp_username = fields.Char('FTP 用户名')
ftp_password = fields.Char('FTP 密码')
qr_code_image = fields.Binary(string='二维码', compute='_generate_qr_code')
@api.depends('name')
def _generate_qr_code(self):
for record in self:
# Generate QR code
qr = qrcode.QRCode(
version=1,
error_correction=qrcode.constants.ERROR_CORRECT_L,
box_size=10,
border=4,
)
qr.add_data(record.MTcode)
qr.make(fit=True)
qr_image = qr.make_image(fill_color="black", back_color="white")
# Encode the image data in base64
image_stream = BytesIO()
qr_image.save(image_stream, format="PNG")
encoded_image = base64.b64encode(image_stream.getvalue())
record.qr_code_image = encoded_image
class SfRobotAxisNum(models.Model):
_name = 'sf.robot.axis.num'

View File

@@ -1053,6 +1053,21 @@
</page>
</xpath>
<xpath expr="//group/field[@name='location']" position="after">
<field name="qr_code_image" widget="image" readonly="1" attrs="{'invisible': [('equipment_type', '!=', '机床')]}" />
</xpath>
<xpath expr="//page[@name='maintenance']" position="after">
<page name="network_config" string="网络配置" attrs="{'invisible': [('equipment_type', '!=', '机床')]}" >
<group>
<group string="ftp配置">
<field name="ftp_host" string="主机"/>
<field name="ftp_port" string="端口"/>
<field name="ftp_username" string="用户名"/>
<field name="ftp_password" string="密码" password="True"/>
</group>
</group>
</page>
</xpath>
</data>
</field>
</record>

View File

@@ -10,7 +10,8 @@
""",
'category': 'sf',
'website': 'https://www.sf.jikimo.com',
'depends': ['sf_base', 'sf_maintenance', 'web_widget_model_viewer', 'sf_warehouse','jikimo_attachment_viewer', 'jikimo_sale_multiple_supply_methods'],
'depends': ['sf_base', 'sf_maintenance', 'web_widget_model_viewer', 'sf_warehouse', 'jikimo_attachment_viewer',
'jikimo_sale_multiple_supply_methods', 'product'],
'data': [
'data/cron_data.xml',
'data/stock_data.xml',
@@ -18,6 +19,7 @@
'data/panel_data.xml',
'data/sf_work_individuation_page.xml',
'data/agv_scheduling_data.xml',
'data/product_data.xml',
'security/group_security.xml',
'security/ir.model.access.csv',
'wizard/workpiece_delivery_views.xml',
@@ -28,6 +30,7 @@
'wizard/mrp_workorder_batch_replan_wizard_views.xml',
'wizard/sf_programming_reason_views.xml',
'wizard/sale_order_cancel_views.xml',
'wizard/process_outsourcing.xml',
'views/mrp_views_menus.xml',
'views/agv_scheduling_views.xml',
'views/stock_lot_views.xml',
@@ -44,6 +47,7 @@
'views/sale_order_views.xml',
'views/mrp_workorder_batch_replan.xml',
'views/purchase_order_view.xml',
'views/product_template_views.xml',
],
'assets': {

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data noupdate="1">
<record id="product_category_service" model="product.category">
<field name="name">服务</field>
<field name="parent_id" ref="product.product_category_all"/>
<field name="property_cost_method">fifo</field>
<field name="property_valuation">manual_periodic</field>
</record>
<record id="product_category_outsource_process" model="product.category">
<field name="name">工序外协</field>
<field name="parent_id" ref="sf_manufacturing.product_category_service"/>
<field name="property_cost_method">fifo</field>
<field name="property_valuation">manual_periodic</field>
</record>
<record id="product_category_outsource_other_process" model="product.category">
<field name="name">其他</field>
<field name="parent_id" ref="sf_manufacturing.product_category_outsource_process"/>
<field name="property_cost_method">fifo</field>
<field name="property_valuation">manual_periodic</field>
</record>
</data>
</odoo>

View File

@@ -1,57 +1,72 @@
<odoo>
<data noupdate="1">
<record model="sf.work.individuation.page" id="sf_work_individuation_page_1">
<field name="code">PTD</field>
<field name="name">后置三元检测</field>
</record>
<data noupdate="0">
<record model="sf.work.individuation.page" id="sf_work_individuation_page_2">
<field name="code">WCP</field>
<field name="name">工件装夹</field>
<field name="sequence">10</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_3">
<field name="code">ITD_PP</field>
<field name="name">前置三元检测定位参数</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_4">
<field name="code">2D_MD</field>
<field name="name">2D加工图纸</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_5">
<field name="code">QIS</field>
<field name="name">质检标准</field>
<field name="sequence">20</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_6">
<field name="code">WD</field>
<field name="name">工件配送</field>
<field name="sequence">30</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_9">
<field name="code">CNC_P</field>
<field name="name">CNC程序</field>
<field name="sequence">40</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_10">
<field name="code">CMM_P</field>
<field name="name">CMM程序</field>
<field name="sequence">50</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_11">
<field name="code">MTI</field>
<field name="name">机床信息</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_12">
<field name="code">HDR</field>
<field name="name">下发记录</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_13">
<field name="code">ER</field>
<field name="name">异常记录</field>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_1">
<field name="code">PTD</field>
<field name="name">后置三元检测</field>
<field name="sequence">60</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_14">
<field name="code">DCP</field>
<field name="name">解除装夹</field>
<field name="sequence">70</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_4">
<field name="code">2D_MD</field>
<field name="name">2D加工图纸</field>
<field name="sequence">80</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_5">
<field name="code">QIS</field>
<field name="name">质检标准</field>
<field name="sequence">90</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_15">
<field name="code">CMR</field>
<field name="name">开料要求</field>
<field name="sequence">100</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_11">
<field name="code">MTI</field>
<field name="name">机床信息</field>
<field name="sequence">110</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_12">
<field name="code">HDR</field>
<field name="name">下发记录</field>
<field name="sequence">120</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_13">
<field name="code">ER</field>
<field name="name">异常记录</field>
<field name="sequence">130</field>
</record>
<!-- 原生页签先不进行配置 -->
<!-- <record model="sf.work.individuation.page" id="sf_work_individuation_page_7">-->

View File

@@ -16,4 +16,5 @@ from . import sf_production_common
from . import sale_order
from . import quick_easy_order
from . import purchase_order
from . import quality_check
from . import quality_check
from . import purchase_request_line

View File

@@ -87,11 +87,12 @@ class AgvScheduling(models.Model):
agv_route_type: AGV任务类型
workorders: 工单
"""
scheduling = None
_logger.info('创建AGV调度任务\r\n起点为【%s】,任务类型为【%s】,工单为【%s' % (agv_start_site_name, agv_route_type, workorders))
if not workorders:
raise UserError(_('工单不能为空'))
agv_start_site = self.env['sf.agv.site'].sudo().search([('name', '=', agv_start_site_name)], limit=1)
if not agv_start_site:
agv_start_sites = self.env['sf.agv.site'].sudo().search([('name', '=', agv_start_site_name)])
if not agv_start_sites:
raise UserError(_('不存在名称为【%s】的接驳站,请先创建!' % agv_start_site_name))
# 如果存在相同任务类型工单的AGV调度任务则提示错误
agv_scheduling = self.sudo().search([
@@ -107,24 +108,32 @@ class AgvScheduling(models.Model):
(','.join(repetitive_workorders.mapped('production_id.name')), agv_scheduling.name)
)
# 如果只有唯一任务路线,则自动赋予终点接驳站跟任务名称
agv_routes = self.env['sf.agv.task.route'].sudo().search([
('route_type', '=', agv_route_type),
('start_site_id', 'in', agv_start_sites.ids)
])
vals = {
'start_site_id': agv_start_site.id,
'agv_route_type': agv_route_type,
'workorder_ids': workorders.ids,
# 'workpiece_delivery_ids': deliveries.mapped('id') if deliveries else [],
'task_create_time': fields.Datetime.now()
}
# 如果只有唯一任务路线,则自动赋予终点接驳站跟任务名称
agv_routes = self.env['sf.agv.task.route'].sudo().search([
('route_type', '=', agv_route_type),
('start_site_id', '=', agv_start_site.id)
])
if not agv_routes:
raise UserError(_('不存在起点为【%s】的【%s】任务路线,请先创建!' % (agv_start_site_name, agv_route_type)))
# 如果路线中包含起点与终点相同的接驳站则不创建AGV调度任务
if agv_routes.filtered(lambda r: r.start_site_id.name == r.end_site_id.name):
return True
# 配送类型相同的接驳站为同一个,取第一个即可
vals.update({
'start_site_id': agv_routes[0].start_site_id.id,
})
idle_route = None
if len(agv_routes) == 1:
idle_route = agv_routes[0]
vals.update({'end_site_id': idle_route.end_site_id.id, 'agv_route_id': idle_route.id})
vals.update({
'end_site_id': idle_route.end_site_id.id, 'agv_route_id': idle_route.id
})
else:
# 判断终点接驳站是否为空闲
idle_routes = agv_routes.filtered(lambda r: r.end_site_id.state == '空闲')
@@ -132,7 +141,10 @@ class AgvScheduling(models.Model):
# 将空闲的路线按照终点接驳站名称排序
idle_routes = sorted(idle_routes, key=lambda r: r.end_site_id.name)
idle_route = idle_routes[0]
vals.update({'end_site_id': idle_route.end_site_id.id, 'agv_route_id': idle_route.id})
vals.update({
'end_site_id': idle_route.end_site_id.id, 'agv_route_id': idle_route.id
})
try:
scheduling = self.env['sf.agv.scheduling'].sudo().create(vals)
# 触发空闲接驳站状态更新,触发新任务下发
@@ -142,7 +154,7 @@ class AgvScheduling(models.Model):
except Exception as e:
_logger.error('添加AGV调度任务失败: %s', e)
raise UserError(_('添加AGV调度任务失败: %s', e))
return scheduling
def on_site_state_change(self, agv_site_id, agv_site_state):

View File

@@ -24,7 +24,7 @@ class AgvSetting(models.Model):
# name必须唯一
_sql_constraints = [
('name_uniq', 'unique (name)', '站点编号必须唯一!'),
('name_uniq', 'unique (name, workcenter_id)', '同一工作中心的站点编号必须唯一!'),
]
# def update_site_state(self):
@@ -68,11 +68,12 @@ class AgvSetting(models.Model):
"""
if isinstance(agv_site_state_arr, dict):
for agv_site_name, is_occupy in agv_site_state_arr.items():
agv_site = self.env['sf.agv.site'].sudo().search([('name', '=', agv_site_name)])
if agv_site:
agv_site.state = is_occupy
agv_sites = self.env['sf.agv.site'].sudo().search([('name', '=', agv_site_name)])
if agv_sites:
agv_sites.state = is_occupy
if notify:
self.env['sf.agv.scheduling'].on_site_state_change(agv_site.id, agv_site.state)
for agv_site in agv_sites:
self.env['sf.agv.scheduling'].on_site_state_change(agv_site.id, agv_site.state)
else:
_logger.error("更新失败:接驳站站点错误!%s" % agv_site_name)
raise UserError("更新失败:接驳站站点错误!")

View File

@@ -6,6 +6,7 @@ import json
import os
import re
import traceback
from operator import itemgetter
import requests
from itertools import groupby
@@ -238,7 +239,8 @@ class MrpProduction(models.Model):
programming_no = fields.Char('编程单号')
work_state = fields.Char('业务状态')
programming_state = fields.Selection(
[('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'), ('已下发', '已下发'), ('已取消', '已取消')],
[('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'), ('已下发', '已下发'),
('已取消', '已取消')],
string='编程状态',
tracking=True)
glb_file = fields.Binary("glb模型文件")
@@ -267,6 +269,7 @@ class MrpProduction(models.Model):
quality_standard = fields.Binary('质检标准', related='product_id.quality_standard', readonly=True)
part_name = fields.Char(string='零件名称', compute='_compute_part_info', store=True)
@api.depends('product_id')
def _compute_part_info(self):
try:
@@ -400,8 +403,10 @@ class MrpProduction(models.Model):
and production.schedule_state == '已排' and production.is_rework is False):
production.state = 'pending_cam'
if any((wo.test_results == '返工' and wo.state == 'done' and
(production.programming_state in ['已编程'] or(wo.individuation_page_list and 'PTD' in wo.individuation_page_list)))
or (wo.is_rework is True and wo.state == 'done' and production.programming_state in ['编程中', '已编程'])
(production.programming_state in ['已编程'] or (
wo.individuation_page_list and 'PTD' in wo.individuation_page_list)))
or (wo.is_rework is True and wo.state == 'done' and production.programming_state in ['编程中',
'已编程'])
for wo in production.workorder_ids) or production.is_rework is True:
production.state = 'rework'
if any(wo.test_results == '报废' and wo.state == 'done' for wo in production.workorder_ids):
@@ -617,7 +622,7 @@ class MrpProduction(models.Model):
for rp in reproduction:
if rp.programming_no == item['programming_no']:
rp.write({'programming_state': '已编程未下发' if item[
'programming_state'] == '已编程' else '编程中'})
'programming_state'] == '已编程' else '编程中'})
else:
return item
@@ -889,11 +894,48 @@ class MrpProduction(models.Model):
workorders_values.append(
self.env[
'mrp.workorder']._json_workorder_surface_process_str(
production, route, product_production_process.seller_ids[0].partner_id.id))
production, route, product_production_process.seller_ids[
0].partner_id.id if product_production_process.seller_ids else False))
production.workorder_ids = workorders_values
for workorder in production.workorder_ids:
workorder.duration_expected = workorder._get_duration_expected()
def _create_subcontract_purchase_request(self, purchase_request_line):
sorted_list = sorted(purchase_request_line, key=itemgetter('name'))
grouped_purchase_request_line = {
k: list(g)
for k, g in groupby(sorted_list, key=itemgetter('name'))
}
for name, request_line in grouped_purchase_request_line.items():
request_line_sorted_list = sorted(request_line, key=itemgetter('product_id'))
grouped_purchase_request_line_sorted_list = {
k: list(g)
for k, g in groupby(request_line_sorted_list, key=itemgetter('product_id'))
}
purchase_request_model = self.env["purchase.request"]
origin = ", ".join({item['production_name'] for item in request_line_sorted_list if item.get('production_name')})
pr = purchase_request_model.create({
"origin": origin,
"company_id": self.company_id.id,
"picking_type_id": self.env.ref('stock.picking_type_in').id,
"group_id": request_line[0].get('group_id'),
"requested_by": self.env.context.get("uid", self.env.uid),
"assigned_to": False,
"bom_id": self[0].bom_id.id,
"is_subcontract":True,
})
self[0].bom_id.bom_line_ids.product_id.route_ids = [(4,self.env.ref(
'sf_stock.stock_route_process_outsourcing').id)]
for product_id, request_line_list in grouped_purchase_request_line_sorted_list.items():
cur_request_line = request_line_list[0]
cur_request_line['product_qty'] = len(request_line_list)
cur_request_line['request_id'] = pr.id
cur_request_line['origin'] = ", ".join({item['production_name'] for item in request_line_list if item.get('production_name')})
cur_request_line.pop('group_id', None)
cur_request_line.pop('production_name', None)
self.env["purchase.request.line"].create(cur_request_line)
pr.button_approved()
# 外协出入库单处理
def get_subcontract_pick_purchase(self):
production_all = self.sorted(lambda x: x.id)
@@ -903,6 +945,8 @@ class MrpProduction(models.Model):
for product_id, pd in grouped_product_ids.items():
product_id_to_production_names[product_id] = [p.name for p in pd]
sorted_workorders = None
purchase_request_line = []
all_workorders = []
for production in production_all:
proc_workorders = []
process_parameter_workorder = self.env['mrp.workorder'].search(
@@ -918,9 +962,14 @@ class MrpProduction(models.Model):
if not sorted_workorders:
return
for workorders in reversed(sorted_workorders):
self.env['stock.picking'].create_outcontract_picking(workorders, production, sorted_workorders)
self.env['purchase.order'].get_purchase_order(workorders, production, product_id_to_production_names)
# self.env['stock.picking'].create_outcontract_picking(workorders, production, sorted_workorders)
# self.env['purchase.order'].get_purchase_order(workorders, production, product_id_to_production_names)
purchase_request_line = purchase_request_line + self.env['purchase.order'].get_purchase_request(
workorders, production)
all_workorders += workorders
self._create_subcontract_purchase_request(purchase_request_line)
for workorder in all_workorders:
workorder._compute_pr_mp_count()
# 工单排序
def _reset_work_order_sequence1(self, k):
for rec in self:
@@ -1341,7 +1390,7 @@ class MrpProduction(models.Model):
'default_reprogramming_num': cloud_programming.get('reprogramming_num') if cloud_programming else '',
'default_programming_state': cloud_programming.get('programming_state') if cloud_programming else '',
'default_is_reprogramming': True if cloud_programming and (
cloud_programming.get('programming_state') in ['已下发']) else False
cloud_programming.get('programming_state') in ['已下发']) else False
}
}
@@ -1728,7 +1777,8 @@ class MrpProduction(models.Model):
raise UserError('仅支持选择单个制造订单进行编程申请,请重新选择')
for production in self:
if production.state not in ['confirmed', 'pending_cam'] or production.programming_state != '已编程':
raise UserError('不可操作。所选制造订单必须同时满足如下条件:\n1、制造订单状态待排程 或 待加工;\n2、制造订单编程状态已编程。\n请检查!')
raise UserError(
'不可操作。所选制造订单必须同时满足如下条件:\n1、制造订单状态待排程 或 待加工;\n2、制造订单编程状态已编程。\n请检查!')
cloud_programming = production._cron_get_programming_state()
if cloud_programming['programming_state'] in ['待编程', '已编程', '编程中']:
raise UserError("当前编程单正在重新编程,请注意查看当前制造订单的“编程记录”确认进度!")
@@ -1771,6 +1821,8 @@ class MrpProduction(models.Model):
except Exception as e:
logging.info('update_programming_state error:%s' % e)
raise UserError("更新编程单状态失败,请联系管理员")
model_id = fields.Char('模型ID', related='product_id.model_id')
# 编程记录

View File

@@ -1,6 +1,7 @@
import logging
from odoo import fields, models, api
from odoo.exceptions import UserError
from odoo.tools import str2bool
class ResMrpRoutingWorkcenter(models.Model):
@@ -24,10 +25,41 @@ class ResMrpRoutingWorkcenter(models.Model):
workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_route', required=True)
bom_id = fields.Many2one('mrp.bom', required=False)
surface_technics_id = fields.Many2one('sf.production.process', string="表面工艺")
optional_process_parameters = fields.One2many('sf.production.process.parameter','routing_id',string='可选工艺参数')
reserved_duration = fields.Float('预留时长', default=30, tracking=True)
is_outsource = fields.Boolean('外协', default=False)
individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录')
@api.onchange('surface_technics_id')
def optional_process_parameters_date(self):
for record in self:
if not record.surface_technics_id:
continue
parameter_ids = self.env['sf.production.process.parameter'].search([
('process_id', '=', record.surface_technics_id.id),
])
record.optional_process_parameters = parameter_ids.ids
# @api.model
# def _auto_init(self):
# # 先执行标准初始化
# res = super(ResMrpRoutingWorkcenter, self)._auto_init()
# # 然后执行自定义初始化
# records = self.search([])
# if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_workcenter',
# default='False')):
# return
# records.optional_process_parameters_date()
# self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_workcenter', True)
# return res
# def init(self):
# super(ResMrpRoutingWorkcenter, self).init()
# # 在模块初始化时触发计算字段的更新
# records = self.search([])
# if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_workcenter',default='False')):
# return
# records.optional_process_parameters_date()
# self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_workcenter', True)
def get_no(self):
international_standards = self.search(
[('code', '!=', ''), ('active', 'in', [True, False])],
@@ -107,6 +139,8 @@ class ResMrpRoutingWorkcenter(models.Model):
class WorkIndividuationPage(models.Model):
_name = 'sf.work.individuation.page'
_order = 'sequence'
code = fields.Char('编号')
name = fields.Char('名称')
sequence = fields.Integer('序号')

View File

@@ -21,7 +21,16 @@ class ResWorkcenter(models.Model):
related='equipment_id.production_line_id', store=True)
is_process_outsourcing = fields.Boolean('工艺外协')
users_ids = fields.Many2many("res.users", 'users_workcenter', tracking=True)
@api.constrains('name')
def _check_unique_name_code(self):
for record in self:
# 检查是否已经存在相同的 name 和 code 组合
existing = self.search([
('name', '=', record.name),
('id', '!=', record.id) # 排除当前记录
])
if existing:
raise ValueError('记录已存在')
def write(self, vals):
if 'users_ids' in vals:
old_users = self.users_ids
@@ -247,14 +256,12 @@ class ResWorkcenter(models.Model):
date_planned_end),
('state', 'not in', ['draft', 'cancel'])])
if plan_ids:
sum_qty = sum([p.product_qty for p in plan_ids])
production_line_hour_capacity = self.production_line_hour_capacity
if sum_qty >= production_line_hour_capacity:
message = '当前计划开始时间不能预约排程,超过生产线小时产能(%d件)%d' % (
production_line_hour_capacity, count)
raise UserError(message)
return False
sum_qty = sum([p.product_qty for p in plan_ids]) if plan_ids else count
production_line_hour_capacity = self.production_line_hour_capacity
if sum_qty > production_line_hour_capacity:
message = '当前计划开始时间不能预约排程,超过生产线小时产能(%d件)%d' % (
production_line_hour_capacity, count)
raise UserError(message)
return True

View File

@@ -19,8 +19,8 @@ from odoo.addons.sf_mrs_connect.models.ftp_operate import FtpController
class ResMrpWorkOrder(models.Model):
_inherit = 'mrp.workorder'
_order = 'sequence asc'
_description = '工单'
_order = 'sequence'
product_tmpl_name = fields.Char('坯料产品名称', related='production_bom_id.bom_line_ids.product_id.name')
@@ -70,7 +70,21 @@ class ResMrpWorkOrder(models.Model):
delivery_warning = fields.Selection([('normal', '正常'), ('warning', '告警'), ('overdue', '逾期')], string='时效',
tracking=True)
back_button_display = fields.Boolean(default=False, compute='_compute_back_button_display', store=True)
pr_mp_count = fields.Integer('采购申请单数量', compute='_compute_pr_mp_count', store=True)
@api.depends('state')
def _compute_pr_mp_count(self):
for item in self:
if not item.is_subcontract:
item.pr_mp_count = 0
continue
pr_ids = self.env['purchase.request'].sudo().search(
[('origin', 'like', item.production_id.name), ('is_subcontract', '=', 'True'),
('state', '!=', 'rejected')])
if pr_ids:
item.pr_mp_count = len(pr_ids)
else:
item.pr_mp_count = 0
@api.depends('state')
def _compute_back_button_display(self):
for record in self:
@@ -106,11 +120,11 @@ class ResMrpWorkOrder(models.Model):
record.back_button_display = False
# tag_type
if cur_workorder.is_subcontract or cur_workorder.routing_type == '解除装夹' or cur_workorder.routing_type == '切割' or any(
detection_result.processing_panel == cur_workorder.processing_panel and
detection_result.routing_type == cur_workorder.routing_type and
cur_workorder.tag_type !='重新加工' and
detection_result.test_results != '合格'
for detection_result in cur_workorder.production_id.detection_result_ids
detection_result.processing_panel == cur_workorder.processing_panel and
detection_result.routing_type == cur_workorder.routing_type and
cur_workorder.tag_type != '重新加工' and
detection_result.test_results != '合格'
for detection_result in cur_workorder.production_id.detection_result_ids
):
record.back_button_display = False
else:
@@ -122,11 +136,11 @@ class ResMrpWorkOrder(models.Model):
else:
record.back_button_display = False
if cur_workorder.is_subcontract or cur_workorder.routing_type == '解除装夹' or cur_workorder.routing_type == '切割' or any(
detection_result.processing_panel == cur_workorder.processing_panel and
detection_result.routing_type == cur_workorder.routing_type and
cur_workorder.tag_type !='重新加工' and
detection_result.test_results != '合格'
for detection_result in cur_workorder.production_id.detection_result_ids
detection_result.processing_panel == cur_workorder.processing_panel and
detection_result.routing_type == cur_workorder.routing_type and
cur_workorder.tag_type != '重新加工' and
detection_result.test_results != '合格'
for detection_result in cur_workorder.production_id.detection_result_ids
):
record.back_button_display = False
@@ -429,6 +443,8 @@ class ResMrpWorkOrder(models.Model):
def _compute_surface_technics_purchase_ids(self):
for order in self:
if order.routing_type == '表面工艺' and order.state not in ['cancel']:
# domain = [('group_id', '=', self.production_id.procurement_group_id.id),
# ('purchase_type', '=', 'consignment'), ('state', '!=', 'cancel')]
domain = [('purchase_type', '=', 'consignment'),
('origin', 'like', '%' + self.production_id.name + '%'),
('state', '!=', 'cancel')]
@@ -444,6 +460,30 @@ class ResMrpWorkOrder(models.Model):
else:
order.surface_technics_purchase_count = 0
def action_view_pr_mrp_workorder(self):
"""
采购请求
"""
self.ensure_one()
pr_ids = self.env['purchase.request'].sudo().search(
[('origin', 'like', self.production_id.name), ('is_subcontract', '=', 'True'),
('state', '!=', 'rejected')])
action = {
'res_model': 'purchase.request',
'type': 'ir.actions.act_window',
}
if len(pr_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': pr_ids[0].id,
})
else:
action.update({
'name': _("%s生成采购请求单", self.name),
'domain': [('id', 'in', pr_ids)],
'view_mode': 'tree,form',
})
return action
def action_view_surface_technics_purchase(self):
self.ensure_one()
# if self.routing_type == '表面工艺':
@@ -473,13 +513,14 @@ class ResMrpWorkOrder(models.Model):
def _get_surface_technics_purchase_ids(self):
domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment')]
purchase_orders = self.env['purchase.order'].search(domain)
# domain = [('group_id', '=', self.production_id.procurement_group_id.id), ('purchase_type', '=', 'consignment')]
purchase_orders = self.env['purchase.order'].search(domain, order='id desc', # 按创建时间降序(最新的在前)
limit=1)
purchase_orders_id = self.env['purchase.order']
for po in purchase_orders:
for line in po.order_line:
if line.product_id.server_product_process_parameters_id == self.surface_technics_parameters_id:
if line.product_qty == 1:
purchase_orders_id = line.order_id
purchase_orders_id = line.order_id
return purchase_orders_id
supplier_id = fields.Many2one('res.partner', string='外协供应商')
@@ -1200,8 +1241,10 @@ class ResMrpWorkOrder(models.Model):
'cmm_ids': production.workorder_ids.filtered(lambda t: t.routing_type == 'CNC加工').cmm_ids,
}]
return workorders_values_str
def _process_compute_state(self):
for workorder in self:
sorted_workorders = sorted(self, key=lambda x: x.sequence)
for workorder in sorted_workorders:
# 如果工单的工序没有进行排序则跳出循环
if workorder.production_id.workorder_ids.filtered(lambda wk: wk.sequence == 0):
continue
@@ -1224,7 +1267,7 @@ class ResMrpWorkOrder(models.Model):
and workorder.production_id.schedule_state == '已排'
and len(workorder.production_id.picking_ids.filtered(
lambda w: w.state not in ['done', 'cancel'])) == 0):
# and workorder.production_id.programming_state == '已编程'
# and workorder.production_id.programming_state == '已编程'
if workorder.is_subcontract is True:
if workorder.production_id.state == 'rework':
workorder.state = 'waiting'
@@ -1260,13 +1303,6 @@ class ResMrpWorkOrder(models.Model):
workorder.state = 'ready'
elif workorder.state != 'waiting':
workorder.state = 'waiting'
# =========== 特殊工艺工单处理 ===================
# if workorder.routing_type == '表面工艺' and workorder.is_subcontrac:
# purchase_order = self.env['purchase.order'].search(
# [('origin', 'ilike', workorder.production_id.name)])
# if purchase_order.picking_ids.filtered(lambda p: p.state in ['waiting', 'confirmed', 'assigned']):
# workorder.state = 'waiting'
# continue
if workorder.technology_design_id.routing_tag == 'special':
if workorder.is_subcontract is False:
workorder.state = 'ready'
@@ -1287,6 +1323,8 @@ class ResMrpWorkOrder(models.Model):
mo.get_move_line(workorder.production_id, workorder))
else:
workorder.state = 'waiting'
@api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state',
'production_id.tool_state', 'production_id.schedule_state', 'sequence',
'production_id.programming_state')
@@ -1301,6 +1339,7 @@ class ResMrpWorkOrder(models.Model):
for check_id in workorder.check_ids:
if not check_id.is_inspect:
check_id.quality_state = 'none'
# 重写工单开始按钮方法
def button_start(self):
# 判断工单状态是否为等待组件
@@ -1534,7 +1573,7 @@ class ResMrpWorkOrder(models.Model):
# workorder.rfid_code_old = rfid_code
# workorder.rfid_code = False
logging.info('workorder.rfid_code:%s' % workorder.rfid_code)
if is_production_id is True:
logging.info('product_qty:%s' % record.production_id.product_qty)
for move_raw_id in record.production_id.move_raw_ids:
@@ -1552,7 +1591,8 @@ class ResMrpWorkOrder(models.Model):
# 如果工单包含了外协工序,需要预留数量
if self.move_raw_ids.move_orig_ids.subcontract_workorder_id:
location_id = self.move_raw_ids.location_id
quant = self.move_raw_ids.lot_ids.quant_ids.filtered(lambda q: q.location_id.id == location_id.id)
quant = self.move_raw_ids.lot_ids.quant_ids.filtered(
lambda q: q.location_id.id == location_id.id)
if quant.reserved_quantity == 0:
self.env['stock.quant']._update_reserved_quantity(
self.move_raw_ids.product_id,
@@ -1706,7 +1746,8 @@ class ResMrpWorkOrder(models.Model):
store=True, string='工序作业')
individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录',
related='routing_work_center_id.individuation_page_ids')
individuation_page_list = fields.Char('个性化记录', default='', compute='_compute_individuation_page_ids', store=True)
individuation_page_list = fields.Char('个性化记录', default='', compute='_compute_individuation_page_ids',
store=True)
@api.depends('name')
def _compute_routing_work_center_id(self):
@@ -1727,6 +1768,7 @@ class ResMrpWorkOrder(models.Model):
individuation_page_list = [item.code for item in mw.individuation_page_ids]
if individuation_page_list:
mw.individuation_page_list = list(set(individuation_page_list))
# =============================================================================================
is_inspect = fields.Boolean('需送检', compute='_compute_is_inspect', store=True, default=False)
@@ -1750,6 +1792,25 @@ class ResMrpWorkOrder(models.Model):
self.check_ids.filtered(lambda ch: ch.is_inspect is True and ch.quality_state == 'waiting').write(
{'quality_state': 'none'})
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
aggregate_field = 'create_date:max'
if aggregate_field not in fields:
fields.append(aggregate_field)
orderby = "create_date desc"
return super(ResMrpWorkOrder, self).read_group(
domain,
fields,
groupby,
offset=offset,
limit=limit,
orderby=orderby,
lazy=lazy
)
model_id = fields.Char('模型ID', related='production_id.model_id')
class CNCprocessing(models.Model):
_name = 'sf.cnc.processing'
@@ -2040,6 +2101,7 @@ class WorkPieceDelivery(models.Model):
def _get_agv_route_type_selection(self):
return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection']
type = fields.Selection(selection=_get_agv_route_type_selection, string='类型')
delivery_duration = fields.Float('配送时长', compute='_compute_delivery_duration')
status = fields.Selection(

View File

@@ -51,7 +51,7 @@ class ResProductMo(models.Model):
# domain="[('materials_id', '=', materials_id)]")
# cutting_tool_model_id.material_model_id
server_product_process_parameters_id = fields.Many2one('sf.production.process.parameter',
string='表面工艺参数(服务产品)')
string='工艺参数(服务产品)')
model_process_parameters_ids = fields.Many2many('sf.production.process.parameter', 'process_parameter_rel',
string='表面工艺参数')
@@ -787,7 +787,7 @@ class ResProductMo(models.Model):
glb_url = fields.Char('glb文件地址')
area = fields.Float('表面积(m²)')
auto_machining = fields.Boolean('自动化加工(模型识别)', default=False)
model_id = fields.Char('模型id')
model_id = fields.Char('模型ID')
@api.depends('name')
@@ -1067,7 +1067,8 @@ class ResProductMo(models.Model):
raise UserError('产品名称【%s】已存在' % item.name)
if item.categ_type == '表面工艺':
if len(self.search([('server_product_process_parameters_id', '=',
item.server_product_process_parameters_id.id)])) > 1:
item.server_product_process_parameters_id.id),('server_product_process_parameters_id', '!=',
False)])) > 1:
raise UserError('表面工艺参数为【%s】的产品已存在' % item.server_product_process_parameters_id.name)
if "create_product_product" not in self._context:
templates._create_variant_ids()

View File

@@ -59,6 +59,86 @@ class PurchaseOrder(models.Model):
production_id = self.env['mrp.production'].search([('origin', 'in', origins)])
purchase.production_count = len(production_id)
def process_replenish(self,production,total_qty):
record = self
bom_line_id = production.bom_id.bom_line_ids
replenish = self.env['stock.warehouse.orderpoint'].search([
('product_id', '=', bom_line_id.product_id.id),
(
'location_id', '=', self.env.ref('sf_stock.stock_location_outsourcing_material_receiving_area').id),
# ('state', 'in', ['draft', 'confirmed'])
], limit=1)
if not replenish:
replenish_model = self.env['stock.warehouse.orderpoint']
replenish = replenish_model.create({
'product_id': bom_line_id.product_id.id,
'location_id': self.env.ref(
'sf_stock.stock_location_outsourcing_material_receiving_area').id,
'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
'group_id': record.group_id.id,
'qty_to_order': total_qty,
'origin': record.name,
})
else:
replenish.write({
'product_id': bom_line_id.product_id.id,
'location_id': self.env.ref(
'sf_stock.stock_location_outsourcing_material_receiving_area').id,
'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
'group_id': record.group_id.id,
'qty_to_order': total_qty + replenish.qty_to_order,
'origin': record.name + ',' + replenish.origin,
})
replenish.action_replenish()
def outsourcing_service_replenishment(self):
record = self
if record.purchase_type != 'consignment':
return
grouped_lines = {}
for line in record.order_line:
if line.related_product.id not in grouped_lines:
grouped_lines[line.related_product.id] = []
grouped_lines[line.related_product.id].append(line)
for product_id,lines in grouped_lines.items():
production = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1)
if not production:
continue
total_qty = sum(line.product_qty for line in lines)
record.process_replenish(production,total_qty)
for product_id,lines in grouped_lines.items():
productions = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1)
if not productions:
continue
# production.bom_id.bom_line_ids.product_id
location_id = self.env['stock.location'].search([('name', '=', '制造前')])
quants = self.env['stock.quant'].search([
('product_id', '=', product_id),
('location_id', '=', location_id.id)
])
total_qty = sum(quants.mapped('quantity')) # 计算该位置的总库存量
is_available = total_qty > 0
if not is_available:
continue
for production_id in productions:
work_ids = production_id.workorder_ids.filtered(
lambda wk: wk.state not in ['done', 'rework', 'cancel'])
if not work_ids:
continue
min_sequence_wk = min(work_ids, key=lambda wk: wk.sequence)
if min_sequence_wk.is_subcontract:
picking_id = production_id.picking_ids.filtered(
lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
move_out = picking_id.move_id
for mo in move_out:
if mo.state != 'done':
mo.write({'state': 'assigned', 'production_id': False})
if not mo.move_line_ids:
self.env['stock.move.line'].create(
mo.get_move_line(production_id, min_sequence_wk))
# product = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1)
# match = re.search(r'(S\d{5}-\d)',product.name)
# pass
def button_confirm(self):
for record in self:
for line in record.order_line:
@@ -66,6 +146,7 @@ class PurchaseOrder(models.Model):
raise UserError('请对【产品】中的【数量】进行输入')
if line.price_unit <= 0:
raise UserError('请对【产品】中的【单价】进行输入')
record.outsourcing_service_replenishment()
return super(PurchaseOrder, self).button_confirm()

View File

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

View File

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

View File

@@ -755,6 +755,24 @@ class StockPicking(models.Model):
if move_id.product_id.tracking in ['serial', 'lot'] and not move_id.move_line_nosuggest_ids:
move_id.action_show_details()
@api.model
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
aggregate_field = 'create_date:max'
if aggregate_field not in fields:
fields.append(aggregate_field)
orderby = "create_date desc"
return super(StockPicking, self).read_group(
domain,
fields,
groupby,
offset=offset,
limit=limit,
orderby=orderby,
lazy=lazy
)
class ReStockMove(models.Model):
_inherit = 'stock.move'

View File

@@ -193,4 +193,6 @@ access_sf_programming_record,sf_programming_record,model_sf_programming_record,b
access_sf_work_individuation_page,sf_work_individuation_page,model_sf_work_individuation_page,sf_base.group_sf_mrp_user,1,1,1,0
access_sf_work_individuation_page_group_plan_dispatch,sf_work_individuation_page_group_plan_dispatch,model_sf_work_individuation_page,sf_base.group_plan_dispatch,1,1,0,0
access_sf_sale_order_cancel_wizard,sf_sale_order_cancel_wizard,model_sf_sale_order_cancel_wizard,sf_base.group_sf_order_user,1,1,1,0
access_sf_sale_order_cancel_line,sf_sale_order_cancel_line,model_sf_sale_order_cancel_line,sf_base.group_sf_order_user,1,0,1,1
access_sf_sale_order_cancel_line,sf_sale_order_cancel_line,model_sf_sale_order_cancel_line,sf_base.group_sf_order_user,1,0,1,1
access_product_creation_wizard,product_creation_wizard,model_product_creation_wizard,base.group_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
193
194
195
196
197
198

View File

@@ -15,6 +15,23 @@
</field>
</record>
<record id="view_agv_site_form" model="ir.ui.view">
<field name="name">agv.site.form</field>
<field name="model">sf.agv.site</field>
<field name="arch" type="xml">
<form create="false" edit="false">
<sheet>
<group>
<field name="name" readonly="1" required="1"/>
<field name="workcenter_id" readonly="1" required="1" options="{'no_create': True}"/>
<field name="state" readonly="1" required="1"/>
<field name="divide_the_work" readonly="1" required="1"/>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_agv_site_form" model="ir.actions.act_window">
<field name="name">AGV站点</field>
<field name="res_model">sf.agv.site</field>
@@ -39,7 +56,8 @@
<field name="route_type" string="类型" required="1" attrs="{'readonly': [('id', '!=', False)]}"/>
<field name="start_site_id" required="1" options="{'no_create': True}" string="起点接驳站"
attrs="{'readonly': [('id', '!=', False)]}"/>
<field name="end_site_id" required="1" options="{'no_create': True}" string="终点接驳站"/>
<field name="end_site_id" required="1" options="{'no_create': True}" string="终点接驳站"
attrs="{'readonly': [('id', '!=', False)]}"/>
<!-- <field name="destination_production_line_id" required="1" options="{'no_create': True}"-->
<!-- attrs="{'readonly': [('id', '!=', False)]}"/>-->
<field name="workcenter_id"/>

View File

@@ -110,12 +110,12 @@
</xpath>
<xpath expr="//sheet//group//group[2]//label" position="before">
<!-- <field name="process_state"/> -->
<field name="production_type" readonly="1"/>
<field name="state" readonly="1"/>
<!-- <field name="process_state"/> -->
</xpath>
<xpath expr="//sheet//group//group//div[3]" position="after">
<field name="production_type" readonly="1"/>
<field name="production_product_type" invisible="1"/>
<field name="manual_quotation" readonly="1"
attrs="{'invisible': ['|', ('production_type', 'not in', ['自动化产线加工', '人工线下加工']), ('production_product_type', '!=', '成品')]}"/>
@@ -383,7 +383,7 @@
<field name="process_parameters_id"
attrs="{'readonly': [('id', '!=', False),('routing_tag', '=', 'standard')]}"
string="参数" context="{'route_id':route_id,'production_id': production_id}"
options="{'no_create': True}"/>
options="{'no_create': True}" domain="[('routing_id', '=', 'route_id')]"/>
<field name="panel" readonly="1"/>
<field name="routing_tag" readonly="1" widget="badge"
decoration-success="routing_tag == 'standard'"
@@ -455,11 +455,15 @@
<field name="inherit_id" ref="mrp.mrp_production_workorder_tree_editable_view"/>
<field name="arch" type="xml">
<xpath expr="//tree" position="attributes">
<attribute name="default_order">sequence</attribute>
<attribute name="default_order">sequence,create_date desc</attribute>
<attribute name="decoration-warning">delivery_warning == 'warning'</attribute>
<attribute name="decoration-danger">delivery_warning == 'overdue'</attribute>
</xpath>
<!-- <xpath expr="//field[@name='create_date']" position="before">-->
<!-- -->
<!-- </xpath>-->
<xpath expr="//field[@name='state']" position="replace">
<field name="create_date" invisible="True"/>
<field name="delivery_warning" invisible="True"/>
<field name="state" widget="badge" decoration-warning="state == 'progress'"
decoration-success="state == 'done'" decoration-danger="state in ('cancel','rework')"

View File

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

View File

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

View File

@@ -144,6 +144,17 @@
statusbar_visible="pending,waiting,ready,progress,to be detected,done,rework"/>
</xpath>
<xpath expr="//div[@name='button_box']" position="inside">
<button type="object" name="action_view_pr_mrp_workorder" class="oe_stat_button"
icon="fa-credit-card"
groups="base.group_user,sf_base.group_sf_order_user"
attrs="{'invisible': [('pr_mp_count', '=', 0)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="pr_mp_count"/>
</span>
<span class="o_stat_text">采购申请</span>
</div>
</button>
<button type="object" name="action_view_surface_technics_purchase" class="oe_stat_button"
icon="fa-credit-card"
groups="base.group_user,sf_base.group_sf_order_user"
@@ -226,22 +237,6 @@
<!-- string="返工"-->
<!-- attrs='{"invisible": [("rework_flag","=",True)]}' confirm="是否返工"/>-->
</xpath>
<xpath expr="//page[1]" position="before">
<page string="开料要求" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "CMR")]}'>
<group>
<group>
<field name="product_tmpl_id_materials_id" widget="many2one"/>
<field name="product_tmpl_id_materials_type_id" widget="many2one"/>
</group>
<group>
<field name="product_tmpl_id_length"/>
<field name="product_tmpl_id_width"/>
<field name="product_tmpl_id_height"/>
</group>
</group>
</page>
</xpath>
<xpath expr="//label[1]" position="before">
<!-- -->
<field name="production_id" invisible="0"/>
@@ -325,6 +320,8 @@
<xpath expr="//page[1]" position="before">
<field name="results" invisible="1"/>
<field name="individuation_page_list" invisible="1"/>
<page string="工件装夹" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "WCP")]}'>
<group>
<!-- <field name="_barcode_scanned" widget="barcode_handler"/> -->
@@ -503,7 +500,6 @@
<field name='X_deviation_angle' readonly="1"/>
</group>
</page>
<page string="工件配送" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "WD")]}'>
<field name="workpiece_delivery_ids">
<tree editable="bottom">
@@ -523,58 +519,6 @@
</tree>
</field>
</page>
</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"), ("routing_type","!=","装夹预调")]}'/>
</xpath>
<!-- =====原生页签,暂时不进行配置===== -->
<!-- <xpath expr="//page[@name='components']" position="attributes">-->
<!-- <attribute name="attrs">{"invisible": ["!", ("individuation_page_list", "ilike", "ML")]}</attribute>-->
<!-- </xpath>-->
<!-- <xpath expr="//page[@name='time_tracking']" position="attributes">-->
<!-- <attribute name="attrs">{"invisible": ["!", ("individuation_page_list", "ilike", "TT")]}</attribute>-->
<!-- </xpath>-->
<!-- ============================= -->
<xpath expr="//page[1]" position="before">
<field name="results" invisible="1"/>
<field name="individuation_page_list" invisible="1"/>
<page string="后置三元检测" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "PTD")]}'>
<group>
<field name="test_results"
attrs='{"readonly":["&amp;","|",("state","!=","to be detected"), "|",("routing_type","=","CNC加工"),("is_inspect", "=", True),("state","in",["done","rework"])],
"invisible":[("results","!=",False)]}'/>
<!-- <field name="is_remanufacture" attrs='{"invisible":[("test_results","!=","报废")]}'/>-->
<!-- <field name="is_fetchcnc"-->
<!-- attrs='{"invisible":["|",("test_results","=","合格"),("is_remanufacture","=",False)]}'/>-->
<field name="reason"
attrs='{"required":[("test_results","!=","合格")],"invisible":[("test_results","=","合格")],"readonly":[("state","in",("done", "rework"))]}'/>
<field name="detailed_reason"
attrs='{"required":[("test_results","!=","合格")],"invisible":[("test_results","=","合格")],"readonly":[("state","in",("done", "rework"))]}'/>
<!-- <field name="results" readonly="1" attrs='{"invisible":[("results","!=","合格")]}'/>-->
<field name="detection_report" attrs='{"invisible":[("results","!=",False)]}'
widget="pdf_viewer" readonly="1"/>
</group>
<!-- <div class="col-12 col-lg-6 o_setting_box">-->
<!-- <button type="object" class="oe_highlight" name="recreateManufacturingOrWorkerOrder"-->
<!-- string="检测确认"-->
<!-- attrs='{"invisible": ["|","|",("state","!=","progress"),("user_permissions","=",False),("results","=","合格")]}'/>-->
<!-- </div>-->
</page>
<page string="2D加工图纸" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "2D_MD")]}'>
<field name="machining_drawings" widget="adaptive_viewer"/>
</page>
<page string="质检标准" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "QIS")]}'>
<field name="quality_standard" widget="adaptive_viewer"/>
</page>
</xpath>
<xpath expr="//page[1]" position="before">
<page string="CNC程序" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "CNC_P")]}'>
<field name="cnc_ids" widget="one2many" string="工作程序" default_order="sequence_number,id"
readonly="0">
@@ -610,8 +554,28 @@
</tree>
</field>
</page>
</xpath>
<xpath expr="//page[1]" position="before">
<page string="后置三元检测" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "PTD")]}'>
<group>
<field name="test_results"
attrs='{"readonly":["&amp;","|",("state","!=","to be detected"), "|",("routing_type","=","CNC加工"),("is_inspect", "=", True),("state","in",["done","rework"])],
"invisible":[("results","!=",False)]}'/>
<!-- <field name="is_remanufacture" attrs='{"invisible":[("test_results","!=","报废")]}'/>-->
<!-- <field name="is_fetchcnc"-->
<!-- attrs='{"invisible":["|",("test_results","=","合格"),("is_remanufacture","=",False)]}'/>-->
<field name="reason"
attrs='{"required":[("test_results","!=","合格")],"invisible":[("test_results","=","合格")],"readonly":[("state","in",("done", "rework"))]}'/>
<field name="detailed_reason"
attrs='{"required":[("test_results","!=","合格")],"invisible":[("test_results","=","合格")],"readonly":[("state","in",("done", "rework"))]}'/>
<!-- <field name="results" readonly="1" attrs='{"invisible":[("results","!=","合格")]}'/>-->
<field name="detection_report" attrs='{"invisible":[("results","!=",False)]}'
widget="pdf_viewer" readonly="1"/>
</group>
<!-- <div class="col-12 col-lg-6 o_setting_box">-->
<!-- <button type="object" class="oe_highlight" name="recreateManufacturingOrWorkerOrder"-->
<!-- string="检测确认"-->
<!-- attrs='{"invisible": ["|","|",("state","!=","progress"),("user_permissions","=",False),("results","=","合格")]}'/>-->
<!-- </div>-->
</page>
<page string="解除装夹" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "DCP")]}'>
<!-- <field name="tray_id" readonly="1"/>-->
<!-- <div class="col-12 col-lg-6 o_setting_box">-->
@@ -625,7 +589,45 @@
<!-- </div>-->
</page>
<page string="2D加工图纸" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "2D_MD")]}'>
<field name="machining_drawings" widget="adaptive_viewer"/>
</page>
<page string="质检标准" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "QIS")]}'>
<field name="quality_standard" widget="adaptive_viewer"/>
</page>
<page name="CMR" string="开料要求" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "CMR")]}'>
<group>
<group>
<field name="product_tmpl_id_materials_id" widget="many2one"/>
<field name="product_tmpl_id_materials_type_id" widget="many2one"/>
</group>
<group>
<field name="product_tmpl_id_length"/>
<field name="product_tmpl_id_width"/>
<field name="product_tmpl_id_height"/>
</group>
</group>
</page>
</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"), ("routing_type","!=","装夹预调")]}'/>
</xpath>
<!-- =====原生页签,暂时不进行配置===== -->
<!-- <xpath expr="//page[@name='components']" position="attributes">-->
<!-- <attribute name="attrs">{"invisible": ["!", ("individuation_page_list", "ilike", "ML")]}</attribute>-->
<!-- </xpath>-->
<!-- <xpath expr="//page[@name='time_tracking']" position="attributes">-->
<!-- <attribute name="attrs">{"invisible": ["!", ("individuation_page_list", "ilike", "TT")]}</attribute>-->
<!-- </xpath>-->
<!-- ============================= -->
<!-- <xpath expr="//form//sheet//group//group//div[1]" position="after">-->
<!-- <label for="date_start" string="实际加工时间"/>-->
<!-- <div class="oe_inline">-->
@@ -676,6 +678,7 @@
<field name="arch" type="xml">
<field name="product_id" position="after">
<field name="part_number" string="成品零件图号"/>
<field name="model_id" string="模型id"/>
</field>
<xpath expr="//filter[@name='progress']" position="after">
<filter string="待检测" name="state" domain="[('state','=','to be detected')]"/>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record model="ir.ui.view" id="view_product_template_form_inherit_sf_manufacturing">
<field name="name">product.template.product.form.inherit.sf_manufacture</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="sf_sale.view_product_template_form_inherit_sf"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='general_information']/group/group[@name='group_standard_price']/field[@name='product_tag_ids']" position="after">
<field name="categ_type" invisible="1"/>
<field name="model_id" readonly="1" attrs="{'invisible': [('categ_type', '!=', '成品')]}"/>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@@ -101,7 +101,7 @@
action="action_quotations_supply_method"
parent="sale.sale_order_menu"
groups="sf_base.group_production_engineer"
sequence="2"/>
sequence="20"/>
<record id="sale.menu_sale_order" model="ir.ui.menu">
<field name="groups_id" eval="[(4, ref('sf_base.group_production_engineer'))]"/>

View File

@@ -50,6 +50,9 @@
<xpath expr="//field[@name='origin']" position="after">
<field name="retrospect_ref"/>
</xpath>
<xpath expr="//tree" position="attributes">
<attribute name="default_order">create_date desc</attribute>
</xpath>
</field>
</record>

View File

@@ -6,3 +6,4 @@ from . import production_technology_re_adjust_wizard
from . import mrp_workorder_batch_replan_wizard
from . import sf_programming_reason
from . import sale_order_cancel
from . import process_outsourcing

View File

@@ -0,0 +1,34 @@
from odoo import models, fields, api
class ProductCreationWizard(models.TransientModel):
_name = 'product.creation.wizard'
_description = '产品创建向导'
# 唯一需要用户输入的字段:产品类别
categ_id = fields.Many2one(
'product.category',
string='产品类别',
required=True,default=lambda self: self.env.ref(
'sf_manufacturing.product_category_outsource_other_process',
raise_if_not_found=False
).sudo(),
)
process_parameter_id = fields.Many2one('sf.production.process.parameter')
def action_create_product(self):
res_partner = self.env['res.partner'].search([('name','=','湖南傲派自动化设备有限公司')])
name = self.process_parameter_id.process_id.name or self.process_parameter_id.routing_id.name
default_values = {
'detailed_type': 'service',
'name': f"{name}_{self.process_parameter_id.name}",
'invoice_policy': 'delivery',
'categ_id': self.categ_id.id,
'description': f"基于{self.process_parameter_id.name}创建的服务产品",
'sale_ok': True, # 可销售
'purchase_ok': True, # 可采购
'server_product_process_parameters_id': self.process_parameter_id.id,
'seller_ids': [(0, 0, {
# 'delay': 1,
'partner_id': res_partner.id,
'price': 1, })],
}
self.env['product.template'].create(default_values)

View File

@@ -0,0 +1,37 @@
<odoo>
<record id="view_product_creation_wizard_form" model="ir.ui.view">
<field name="name">product.creation.wizard.form</field>
<field name="model">product.creation.wizard</field>
<field name="arch" type="xml">
<form string="创建产品">
<sheet>
<group>
<field name="categ_id" required="1"/>
</group>
</sheet>
<footer>
<button
name="action_create_product"
string="创建"
type="object"
class="btn-primary"
/>
<button
string="取消"
special="cancel"
class="btn-secondary"
/>
</footer>
</form>
</field>
</record>
<!-- 向导动作 -->
<record id="action_product_creation_wizard" model="ir.actions.act_window">
<field name="name">快速创建产品</field>
<field name="res_model">product.creation.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="binding_model_id" ref="product.model_product_product"/>
</record>
</odoo>

View File

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

View File

@@ -117,17 +117,30 @@ class WorkpieceDeliveryWizard(models.TransientModel):
item.button_finish()
# 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'},
if isinstance(scheduling, bool) and scheduling is True:
return{
'type': 'ir.actions.client',
'tag': 'display_notification',
'target': 'new',
'params': {
'message': f'解除装夹成功',
'type': 'success',
'sticky': False,
'next': {'type': 'ir.actions.act_window_close'},
}
}
else:
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(f'{self.delivery_type}任务下发失败:{e}') from e

View File

@@ -8,6 +8,7 @@ from odoo.http import request
from odoo.addons.sf_base.controllers.controllers import MultiInheritController
class Sf_Mrs_Connect(http.Controller, MultiInheritController):
@http.route('/api/cnc_processing/create', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
@@ -22,6 +23,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
try:
res = {'status': 1, 'message': '成功'}
datas = request.httprequest.data
model_id = None
ret = json.loads(datas)
ret = json.loads(ret['result'])
logging.info('下发编程单:%s' % ret)
@@ -57,6 +59,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
res['message'] = '编程单号为%s的CNC程序文件从FTP拉取失败' % (ret['programming_no'])
return json.JSONEncoder().encode(res)
for production in productions:
model_id = production.product_id.model_id # 一个编程单的制造订单对应同一个模型
production.write({'programming_state': '已编程', 'work_state': '已编程', 'is_rework': False})
for panel in ret['processing_panel'].split(','):
# 查询状态为进行中且工序类型为CNC加工的工单
@@ -83,19 +86,23 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
# panel)
program_path_tmp_panel = os.path.join('/tmp', ret['folder_name'], 'return', panel)
files_panel = os.listdir(program_path_tmp_panel)
panel_file_path = ''
if files_panel:
for file in files_panel:
file_extension = os.path.splitext(file)[1]
if file_extension.lower() == '.pdf':
panel_file_path = os.path.join(program_path_tmp_panel, file)
logging.info('panel_file_path:%s' % panel_file_path)
cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
pre_workorder = productions.workorder_ids.filtered(
lambda ap: ap.routing_type in ['装夹预调', '人工线下加工'] and ap.state not in ['done', 'rework'
'cancel'] and ap.processing_panel == panel)
if pre_workorder:
pre_workorder.write(
{'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())})
# 向编程单中添加二维码
request.env['printing.utils'].add_qr_code_to_pdf(panel_file_path, model_id, "扫码获取工单")
cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
pre_workorder = productions.workorder_ids.filtered(
lambda ap: ap.routing_type in ['装夹预调', '人工线下加工'] and ap.state not in ['done', 'rework'
'cancel'] and ap.processing_panel == panel)
if pre_workorder:
pre_workorder.write(
{'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())})
productions.write({'programming_state': '已编程', 'work_state': '已编程'})
productions.filtered(lambda p: p.production_type == '人工线下加工').write({'manual_quotation': True})
logging.info('已更新制造订单编程状态:%s' % productions.ids)
@@ -268,3 +275,6 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
request.cr.rollback()
logging.info('get_cnc_processing_create error:%s' % e)
return json.JSONEncoder().encode(res)

View File

@@ -1,16 +1,16 @@
<?xml version="1.0" encoding='UTF-8'?>
<odoo>
<record model="ir.cron" id="ir_cron_sf_static_resource_datasync">
<field name="name">制造-配置每日定时同步cloud的静态资源库</field>
<field name="model_id" ref="model_res_config_settings"/>
<field name="state">code</field>
<field name="code">model.sf_all_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 noupdate="1">
<record model="ir.cron" id="ir_cron_sf_static_resource_datasync">
<field name="name">制造-配置每日定时同步cloud的静态资源库</field>
<field name="model_id" ref="model_res_config_settings"/>
<field name="state">code</field>
<field name="code">model.sf_all_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>
<!-- <record model="ir.cron" id="sf_cron1">-->
<!-- <field name="name">同步静态资源库材料</field>-->
<!-- <field name="model_id" ref="model_sf_production_materials"/>-->
@@ -220,4 +220,5 @@
<!-- <field name="numbercall">-1</field>-->
<!-- <field name="doall" eval="False"/>-->
<!-- </record>-->
</data>
</odoo>

View File

@@ -1135,8 +1135,10 @@ class sfProductionProcessParameter(models.Model):
[("code", '=', item['code']), ('active', 'in', [True, False])])
process = self.env['sf.production.process'].search(
[('code', '=', item['process_id_code'])], limit=1)
production_process_parameter = self.search(
[("code", '=', item['code']), ('active', 'in', [True, False])])
if not production_process_parameter:
self.create({
production_process_parameter = self.create({
"name": item['name'],
"process_description": item['process_description'],
"processing_day": item['processing_day'],
@@ -1146,9 +1148,12 @@ class sfProductionProcessParameter(models.Model):
"process_id": process.id,
'materials_model_ids': self.env['sf.materials.model'].search(
[('materials_no', 'in', item['materials_model_ids_codes'])]),
'processing_mm': item['processing_mm']
'processing_mm': item['processing_mm'],
'gain_way':'外协',
})
production_process_parameter.create_service_product()
else:
production_process_parameter.gain_way = '外协'
production_process_parameter.name = item['name']
production_process_parameter.process_description = item['process_description']
production_process_parameter.processing_day = item['processing_day']
@@ -1158,6 +1163,9 @@ class sfProductionProcessParameter(models.Model):
[('materials_no', 'in', item['materials_model_ids_codes'])])
production_process_parameter.active = item['active']
production_process_parameter.processing_mm = item['processing_mm']
if not production_process_parameter.outsourced_service_products:
production_process_parameter.create_service_product()
production_process_parameter.create_work_center()
else:
raise ValidationError("表面工艺可选参数认证未通过")

View File

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

View File

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

View File

@@ -24,15 +24,17 @@
<field name="production_line_id"/>
<field name="date_planned_start"/>
<field name="date_planned_finished"/>
<field name="production_type" widget="badge" decoration-warning="production_type == '人工线下加工'" decoration-success="production_type == '自动化产线加工'"/>
<field name="production_type" widget="badge" decoration-warning="production_type == '人工线下加工'"
decoration-success="production_type == '自动化产线加工'"/>
<field name="actual_start_time" optional='hide'/>
<field name="actual_end_time" optional='hide'/>
<field name="actual_process_time" optional='hide'/>
<field name="schedule_setting" optional='hide'/>
<!-- <button name="do_production_schedule" class="btn schedule_done" string="生产排程" type="object" -->
<!-- attrs="{'invisible': ['|', ('state', '!=', 'draft'), ('actual_start_time', '!=', False)]}" -->
<!-- groups="sf_base.group_plan_dispatch"/> -->
<button name="cancel_production_schedule" class="btn schedule_cancel" string="取消排程" type="object"
<!-- <button name="do_production_schedule" class="btn schedule_done" string="生产排程" type="object" -->
<!-- attrs="{'invisible': ['|', ('state', '!=', 'draft'), ('actual_start_time', '!=', False)]}" -->
<!-- groups="sf_base.group_plan_dispatch"/> -->
<button name="cancel_production_schedule" class="btn schedule_cancel" string="取消排程"
type="object"
attrs="{'invisible': ['|', ('state', '!=', 'done'), ('actual_start_time', '!=', False)]}"
groups="sf_base.group_plan_dispatch"/>
</tree>
@@ -48,9 +50,11 @@
<!-- <button string="执行排程" name="do_production_schedule" type="object" class="oe_highlight" icon="fa-step-forward"/> -->
<button string="执行排程" name="do_production_schedule" type="object" class="oe_highlight"
options='{"calendar_view": true, "date_begin": "2020-01-01", "date_end": "2020-12-31"}'
groups="sf_base.group_plan_dispatch" attrs="{'invisible': ['|', ('state', '!=', 'draft'), ('actual_start_time', '!=', False)]}"/>
groups="sf_base.group_plan_dispatch"
attrs="{'invisible': ['|', ('state', '!=', 'draft'), ('actual_start_time', '!=', False)]}"/>
<button string="取消排程" name="cancel_production_schedule" type="object" class="oe_highlight"
groups="sf_base.group_plan_dispatch" attrs="{'invisible': ['|', ('state', '!=', 'done'), ('actual_start_time', '!=', False)]}"/>
groups="sf_base.group_plan_dispatch"
attrs="{'invisible': ['|', ('state', '!=', 'done'), ('actual_start_time', '!=', False)]}"/>
<!-- <button name="archive" type="object" string="归档" icon="fa-archive" class="oe_highlight" attrs="{'invisible': [('active', '=', False)]}"/> -->
<!-- <button name="unarchive" type="object" string="取消归档" icon="fa-archive" class="oe_highlight" attrs="{'invisible': [('active', '=', True)]}"/> -->
@@ -66,13 +70,13 @@
</div>
<group>
<group string="基本信息">
<!-- <field name="active" invisible="1"/> -->
<!-- <field name="active" invisible="1"/> -->
<field name="production_id" widget="many2one_button"/>
<field name="product_id"/>
<field name="origin"/>
<field name="product_qty"/>
<field name="order_deadline" widget="date"/>
<!-- <field name="process_time"/> -->
<!-- <field name="process_time"/> -->
<field name="schedule_setting"/>
<field name="production_line_id" domain="[('name', 'ilike', 'CNC')]"/>
@@ -83,15 +87,16 @@
<!-- <field name="message_ids" widget="mail_thread"/> -->
<!-- <field name="activity_ids"/> -->
<!-- </div> -->
<!-- <div class="oe_chatter"> -->
<!-- <field name="message_follower_ids"/> -->
<!-- <field name="message_ids"/> -->
<!-- </div> -->
<!-- <div class="oe_chatter"> -->
<!-- <field name="message_follower_ids"/> -->
<!-- <field name="message_ids"/> -->
<!-- </div> -->
</group>
<group string="加工信息">
<field name="date_planned_start" placeholder="如果不选择计划开始时间,会取当前时间来做排程" required="1"/>
<field name="date_planned_start"
placeholder="如果不选择计划开始时间,会取当前时间来做排程" required="1"/>
<field name="date_planned_finished" required="0" invisible="1"/>
<field name="actual_process_time"/>
<field name="actual_start_time"/>
@@ -157,8 +162,8 @@
<search string="订单计划">
<!-- Your other filters go here -->
<!-- <filter name="archived" string="已归档" domain="[('active','=',False)]"/> -->
<!-- <filter name="not archived" string="未归档" domain="[('active','=',True)]"/> -->
<!-- <filter name="archived" string="已归档" domain="[('active','=',False)]"/> -->
<!-- <filter name="not archived" string="未归档" domain="[('active','=',True)]"/> -->
<field name="name"/>
<field name="origin"/>
<field name="part_name"/>
@@ -173,13 +178,16 @@
<filter name="group_by_state" string="状态" domain="[]" context="{'group_by': 'state'}"/>
</group>
<group expand="0" string="Group By">
<filter name="group_by_production_line_id" string="生产线" domain="[]" context="{'group_by': 'production_line_id'}"/>
<filter name="group_by_production_line_id" string="生产线" domain="[]"
context="{'group_by': 'production_line_id'}"/>
</group>
<searchpanel>
<!-- <field name="state" icon="fa-filter"/> -->
<field name="production_line_id" select="multi" string="生产线" icon="fa-building" enable_counters="1"/>
<!-- <field name="state" icon="fa-filter"/> -->
<field name="production_line_id" select="multi" string="生产线" icon="fa-building"
enable_counters="1"/>
<field name="state" select="multi" string="状态" icon="fa-building" enable_counters="1"/>
<field name="production_type" select="multi" string="制造类型" icon="fa-building" enable_counters="1"/>
<field name="production_type" select="multi" string="制造类型" icon="fa-building"
enable_counters="1"/>
</searchpanel>
</search>
</field>
@@ -275,7 +283,7 @@
<field name="type">ir.actions.act_window</field>
<field name="res_model">sf.production.plan</field>
<field name="view_mode">tree,gantt,form</field>
<!-- <field name="context">{'search_default_group_by_state': 1, 'search_default_draft': 1, 'display_complete': True}</field> -->
<!-- <field name="context">{'search_default_group_by_state': 1, 'search_default_draft': 1, 'display_complete': True}</field> -->
<field name="context">{'search_default_draft': 1, 'display_complete': True}</field>
</record>
@@ -341,7 +349,7 @@
parent="mrp.menu_mrp_manufacturing"
/>
<menuitem
<menuitem
id="sf_workpiece_delivery__empty_racks_menu"
name="空料架配送"
sequence="11"
@@ -363,6 +371,42 @@
action="sf_production_plan_action1"
parent="sf_production_plan_menu"
/>
<menuitem
id="menu_plan_config_root"
name="配置"
sequence="190"
parent="sf_production_plan_menu"
/>
<!-- 二级菜单:工厂日历 -->
<menuitem
id="menu_factory_calendar"
name="工厂日历"
parent="menu_plan_config_root"
action="resource.action_resource_calendar_form"
sequence="10"
/>
<!-- <record id="action_resource_calendar_form" model="ir.actions.act_window">-->
<!-- <field name="name">Working Times</field>-->
<!-- <field name="res_model">resource.calendar</field>-->
<!-- <field name="view_mode">tree,form</field>-->
<!-- <field name="view_id" eval="False"/>-->
<!-- <field name="search_view_id" ref="view_resource_calendar_search"/>-->
<!-- <field name="help" type="html">-->
<!-- <p class="o_view_nocontent_smiling_face">-->
<!-- Define working hours and time table that could be scheduled to your project members-->
<!-- </p>-->
<!-- </field>-->
<!-- </record>-->
<!-- <record id="action_factory_calendar" model="ir.actions.act_window">-->
<!-- <field name="name">工厂日历</field>-->
<!-- <field name="res_model">resource.calendar</field>-->
<!-- <field name="view_mode">tree,form</field>-->
<!-- <field name="help" type="html">-->
<!-- <p class="o_view_nocontent_smiling_face">-->
<!-- 创建和管理工厂工作日历-->
<!-- </p>-->
<!-- </field>-->
<!-- </record>-->
</data>
</odoo>

View File

@@ -40,6 +40,5 @@ class Action_Plan_All_Wizard(models.TransientModel):
self.plan_ids.date_planned_start = self.date_planned_start
# 在这里添加您的逻辑来处理这些ID
# 判断能否排成
self.plan_ids.deal_processing_schedule(self.date_planned_start)
self.plan_ids.do_production_schedule()
_logger.info('处理计划: %s 完成', self.plan_ids.ids)

View File

@@ -26,6 +26,7 @@
'views/quality_check_view.xml',
'views/quality_company.xml',
'wizard/check_picking_wizard_view.xml',
'wizard/confirmation_wizard_views.xml',
],
'assets': {

View File

@@ -61,6 +61,29 @@
</div>
</div>
</template>
<!-- 定义页脚模板无页码 -->
<template id="html_report_quality_footer">
<div class="footer">
<div style="border-top: 3px solid black;"></div>
<div class="row">
<div class="col-6">
<p>售后服务: <span t-field="o.company_id.phone"/></p>
<p>公司名称: <span t-field="o.company_id.name"/></p>
<p>加工工厂: <span t-field="o.company_id.factory_name"/></p>
</div>
<div class="col-6">
<p>公司网址: <span t-field="o.company_id.website"/></p>
<p>公司邮箱: <span t-field="o.company_id.email"/></p>
</div>
</div>
<!-- <div style="border-top: 2px solid black;"></div> -->
<div class="text-center">
<span><span>1</span> 页/共 <span>1</span></span>
</div>
</div>
</template>
<template id="report_quality_inspection">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
@@ -73,7 +96,7 @@
<table class="table table-sm o_main_table mt-4" style="border: 1px solid black;">
<tr>
<td style="width: 15%; border: 1px solid black;"><strong>产品名称:</strong></td>
<td style="width: 35%; border: 1px solid black;"><span t-field="o.product_id.name"/></td>
<td style="width: 35%; border: 1px solid black;"><span t-field="o.part_name"/></td>
<td style="width: 15%; border: 1px solid black;"><strong>材料:</strong></td>
<td style="width: 35%; border: 1px solid black;"><span t-field="o.material_name"/></td>
</tr>
@@ -187,4 +210,131 @@
</t>
</t>
</template>
<template id="html_report_quality_inspection">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="o">
<t t-call="web.basic_layout">
<t t-call="sf_quality.report_quality_header"/>
<div class="page" style="min-height: 800px; position: relative; padding-bottom: 250px;">
<table class="table table-sm o_main_table mt-4" style="border: 1px solid black;">
<tr>
<td style="width: 15%; border: 1px solid black;"><strong>产品名称:</strong></td>
<td style="width: 35%; border: 1px solid black;"><span t-field="o.part_name"/></td>
<td style="width: 15%; border: 1px solid black;"><strong>材料:</strong></td>
<td style="width: 35%; border: 1px solid black;"><span t-field="o.material_name"/></td>
</tr>
<tr>
<td style="border: 1px solid black;"><strong>图号:</strong></td>
<td style="border: 1px solid black;"><span t-field="o.part_number"/></td>
<td style="border: 1px solid black;"><strong>日期:</strong></td>
<td style="border: 1px solid black;"><span t-field="o.write_date"/></td>
</tr>
<tr>
<td style="border: 1px solid black;"><strong>总数量:</strong></td>
<td style="border: 1px solid black;"><span t-field="o.total_qty"/></td>
<td style="border: 1px solid black;"><strong>检验数量:</strong></td>
<td style="border: 1px solid black;"><span t-field="o.check_qty"/></td>
</tr>
</table>
<h4 class="text-center mt-4">检验结果</h4>
<div class="" style="position: relative;">
<table class="table table-sm mt-2" style="border: 1px solid black;">
<thead>
<tr>
<th style="border: 1px solid black;" class="text-center" rowspan="2">检测项目<br/>(图示尺寸)</th>
<th style="border: 1px solid black;" t-att-colspan="o.column_nums" class="text-center">测量值</th>
<th style="border: 1px solid black; vertical-align: middle;" class="text-center" rowspan="2">判定</th>
<th style="border: 1px solid black; vertical-align: middle;" class="text-center" rowspan="2">备注</th>
</tr>
<tr>
<!-- <th style="border: 1px solid black;"></th> -->
<th style="border: 1px solid black;" t-if="o.column_nums >= 1" class="text-center">1</th>
<th style="border: 1px solid black;" t-if="o.column_nums >= 2" class="text-center">2</th>
<th style="border: 1px solid black;" t-if="o.column_nums >= 3" class="text-center">3</th>
<th style="border: 1px solid black;" t-if="o.column_nums >= 4" class="text-center">4</th>
<th style="border: 1px solid black;" t-if="o.column_nums >= 5" class="text-center">5</th>
<!-- <th style="border: 1px solid black;"></th>
<th style="border: 1px solid black;"></th> -->
</tr>
</thead>
<tbody>
<tr t-foreach="o.measure_line_ids" t-as="line">
<td style="border: 1px solid black;" class="text-center"><span t-field="line.measure_item"/></td>
<td style="border: 1px solid black;" t-if="o.column_nums >= 1" class="text-center"><span t-field="line.measure_value1"/></td>
<td style="border: 1px solid black;" t-if="o.column_nums >= 2" class="text-center"><span t-field="line.measure_value2"/></td>
<td style="border: 1px solid black;" t-if="o.column_nums >= 3" class="text-center"><span t-field="line.measure_value3"/></td>
<td style="border: 1px solid black;" t-if="o.column_nums >= 4" class="text-center"><span t-field="line.measure_value4"/></td>
<td style="border: 1px solid black;" t-if="o.column_nums >= 5" class="text-center"><span t-field="line.measure_value5"/></td>
<td style="border: 1px solid black;" class="text-center"><span t-field="line.measure_result"/></td>
<td style="border: 1px solid black;" class="text-center"><span t-field="line.remark"/></td>
</tr>
</tbody>
</table>
<img src="/sf_quality/static/img/pass.png" style="width: 200px; height: 200px;position: absolute; bottom: 20px; right: 20%;"/>
</div>
<div style="clear: both; margin-top: 30px; padding-top: 10px;">
<div style="display: inline-block;">
<span style="font-size: 18px; font-weight: bold;">检验结论: </span>
<span t-if="o.report_result == 'OK'" style="margin-left: 30px; display: inline-block;">
<svg width="20" height="20" style="vertical-align: middle;">
<rect x="1" y="1" width="18" height="18" fill="none" stroke="black" stroke-width="1.5"/>
<path d="M4 10 L9 15 L16 6" stroke="black" stroke-width="2" fill="none"/>
</svg>
<span style="margin-left: 5px;">合格</span>
</span>
<span t-else="" style="margin-left: 30px; display: inline-block;">
<svg width="20" height="20" style="vertical-align: middle;">
<rect x="1" y="1" width="18" height="18" fill="none" stroke="black" stroke-width="1.5"/>
</svg>
<span style="margin-left: 5px;">合格</span>
</span>
<span t-if="o.report_result == 'NG'" style="margin-left: 50px; display: inline-block;">
<svg width="20" height="20" style="vertical-align: middle;">
<rect x="1" y="1" width="18" height="18" fill="none" stroke="black" stroke-width="1.5"/>
<path d="M4 10 L9 15 L16 6" stroke="black" stroke-width="2" fill="none"/>
</svg>
<span style="margin-left: 5px;">不合格</span>
</span>
<span t-else="" style="margin-left: 50px; display: inline-block;">
<svg width="20" height="20" style="vertical-align: middle;">
<rect x="1" y="1" width="18" height="18" fill="none" stroke="black" stroke-width="1.5"/>
</svg>
<span style="margin-left: 5px;">不合格</span>
</span>
</div>
</div>
<div class="row mt-4">
<div class="col-6">
<p><strong>操作员: </strong> <span t-field="o.measure_operator"/></p>
</div>
<div class="col-6">
<p><strong>质检员: </strong> <span t-field="o.quality_manager"/></p>
</div>
</div>
<div style="border-top: 3px solid black;"></div>
<!-- 添加合格标签 -->
<!-- <div class="row mt-5">
<div class="col-12 text-center">
<p></p>
</div>
</div> -->
<!-- 页脚固定在底部 -->
<!-- <div style="position: absolute; bottom: 0; left: 0; right: 0;"> -->
<t t-call="sf_quality.html_report_quality_footer"/>
<!-- </div> -->
</div>
</t>
</t>
</t>
</template>
</odoo>

View File

@@ -18,7 +18,7 @@
<field name="name">预览检验报告</field>
<field name="model">quality.check</field>
<field name="report_type">qweb-html</field>
<field name="report_name">sf_quality.report_quality_inspection</field>
<field name="report_name">sf_quality.html_report_quality_inspection</field>
<field name="binding_type">report</field>
</record>
</odoo>

View File

@@ -74,5 +74,6 @@ access_quality_cnc_test_group_quality_director,quality_cnc_test_group_quality_di
access_quality_cnc_test_group_sf_equipment_user,quality_cnc_test_group_sf_equipment_user,model_quality_cnc_test,sf_base.group_sf_equipment_user,1,1,0,0
access_picking_validate_check_wizard,access.picking_validate_check_wizard,model_picking_validate_check_wizard,quality.group_quality_user,1,1,1,0
access_confirmation_wizard,access.confirmation.wizard,model_confirmation_wizard,quality.group_quality_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
74
75
76
77
78
79

View File

@@ -1 +1,2 @@
from . import check_picking_wizard
from . import confirmation_wizard

View File

@@ -0,0 +1,32 @@
from odoo import models, fields, api
class ConfirmationWizard(models.TransientModel):
_name = 'confirmation.wizard'
_description = '二次确认向导'
# 可根据需要传递上下文参数
check_id = fields.Many2one('quality.check', string='质检单', required=True)
picking_name = fields.Char(string='拣货单', related='check_id.picking_id.name', store=True)
number = fields.Char(string='数量', related='check_id.total_qty', store=True)
picking_num = fields.Integer(string='拣货数量', compute='_compute_picking_num', store=True)
@api.depends('check_id.picking_id')
def _compute_picking_num(self):
for record in self.check_id:
if record.picking_id:
for move in record.picking_id.move_ids_without_package:
if move.product_id == record.product_id:
self.picking_num = int(move.product_uom_qty)
else:
self.picking_num = 0
button_text = fields.Char(string='确认按钮文字')
def action_confirm(self):
self.ensure_one()
# 获取原始记录
check = self.env['quality.check'].browse(self.check_id.id)
# 调用实际发布方法
return check._do_actual_publish()

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_confirmation_wizard_form" model="ir.ui.view">
<field name="name">confirmation.wizard.form</field>
<field name="model">confirmation.wizard</field>
<field name="arch" type="xml">
<form>
<div class="alert alert-warning">
<p>拣货调拨单号<strong><field name="picking_name" class="oe_inline"/></strong>需求数量为<strong><field name="picking_num" class="oe_inline"/></strong>,当前质量检查单产品数量为<strong><field name="number" class="oe_inline"/></strong>,数量不一致,是否确认继续?</p>
</div>
<footer>
<button name="action_confirm" string="确定" type="object" class="btn-primary"/>
<button string="取消" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@@ -271,7 +271,7 @@ class ResaleOrderLine(models.Model):
embryo_redundancy_id = fields.Many2one('sf.embryo.redundancy', '坯料冗余')
manual_quotation = fields.Boolean('人工编程', default=False)
model_url = fields.Char('模型文件地址')
model_id = fields.Char('模型id')
model_id = fields.Char('模型ID')
delivery_end_date = fields.Date('交货截止日期')
@@ -323,7 +323,7 @@ class RePurchaseOrder(models.Model):
contract_summary = fields.Text(string='合同概况')
# 选择是否为紧急采购
urgent_purchase = fields.Selection([('no', ''), ('yes', '')], string='紧急采购', default='no')
urgent_purchase = fields.Selection([('no', ''), ('yes', '')], string='紧急采购', default='yes')
@api.depends('origin')
def _compute_purchase_type(self):
@@ -343,6 +343,11 @@ class RePurchaseOrder(models.Model):
if order_line.product_id.id in product_list:
purchase.purchase_type = 'outsourcing'
break
request_lines = self.order_line.mapped('purchase_request_lines')
# 检查是否存在 is_subcontract 为 True 的行
if any(line.is_subcontract for line in request_lines):
purchase.purchase_type = 'consignment'
delivery_warning = fields.Selection([('normal', '正常'), ('warning', '预警'), ('overdue', '已逾期')],
string='交期状态', default='normal',
@@ -376,6 +381,29 @@ class RePurchaseOrder(models.Model):
if not line.taxes_id:
raise UserError('请对【产品】中的【税】进行选择')
def get_purchase_request(self, consecutive_process_parameters, production):
result = []
for pp in consecutive_process_parameters:
server_template = self.env['product.template'].search(
[('server_product_process_parameters_id', '=', pp.surface_technics_parameters_id.id),
('detailed_type', '=', 'service')])
# route_ids
result.append({
"product_id": server_template.product_variant_id.id,
'related_product':production.product_id.id,
"name": production.procurement_group_id.name,
"date_required": fields.Datetime.now(),
"product_uom_id":server_template.uom_id.id,
"product_qty": 1,
"request_id": False,
"move_dest_ids": False,
"orderpoint_id": False,
'is_subcontract':True,
'group_id':production.procurement_group_id.id,
'production_name':pp.production_id.name,
})
return result
def get_purchase_order(self, consecutive_process_parameters, production, product_id_to_production_names):
for pp in consecutive_process_parameters:
server_product_process = []
@@ -396,14 +424,14 @@ class RePurchaseOrder(models.Model):
'manual_part_name': pp.part_name,
}))
# 获取服务商品最后一个供应商的采购员
purchase_user_id = server_template.seller_ids[-1].partner_id.purchase_user_id
purchase_user_id = server_template.seller_ids[-1].partner_id.purchase_user_id if server_template.seller_ids else False
purchase_order = self.env['purchase.order'].sudo().create({
'partner_id': server_template.seller_ids[0].partner_id.id,
'partner_id': server_template.seller_ids[0].partner_id.id if server_template.seller_ids else False,
'origin': production.name,
'state': 'draft',
'purchase_type': 'consignment',
'order_line': server_product_process,
'user_id': purchase_user_id.id
'user_id': purchase_user_id.id if purchase_user_id else False,
})
purchase_order.order_line._compute_part_number()
pp.purchase_id = [(6, 0, [purchase_order.id])]

View File

@@ -298,6 +298,10 @@
<xpath expr="//field[@name='name']" position="attributes">
<attribute name="class">purchase_order_list_name</attribute>
</xpath>
<!-- 修改 tree 视图的排序规则 -->
<xpath expr="//tree" position="attributes">
<attribute name="default_order">date_approve desc</attribute>
</xpath>
</field>
</record>

View File

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

View File

@@ -14,16 +14,14 @@
<!-- name="Orders"-->
<!-- sequence="10">-->
<!-- <menuitem id="menu_sale_quotations"-->
<!-- action="action_quotations_with_onboarding"-->
<!-- groups="sales_team.group_sale_salesman"-->
<!-- sequence="10"/>-->
<!-- <menuitem id="menu_sale_order"-->
<!-- name="Orders"-->
<!-- action="action_orders"-->
<!-- groups="sales_team.group_sale_salesman"-->
<!-- sequence="20"/>-->
<menuitem id="sale.menu_sale_quotations"
parent="sale.sale_order_menu"
sequence="20"/>
<menuitem id="sale.menu_sale_order"
parent="sale.sale_order_menu"
sequence="10"/>
<!-- <menuitem id="report_sales_team"-->

View File

@@ -24,6 +24,7 @@
# always loaded
'data': [
# 'security/ir.model.access.csv',
'data/stock_location_data.xml',
'views/stock_picking.xml',
'views/stock_product_template.xml',
],

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="stock_location_outsourcing_material_receiving_area" model="stock.location">
<field name="name">外协收料区</field>
<field name="usage">internal</field>
</record>
<record id="stock_route_process_outsourcing" model='stock.route'>
<field name="name">工序外协</field>
<field name="company_id"></field>
</record>
</data>
</odoo>

View File

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

View File

@@ -0,0 +1,11 @@
import logging
from odoo import models, fields
_logger = logging.getLogger(__name__)
class StockWarehouseOrderpoint(models.Model):
_inherit = 'stock.warehouse.orderpoint'
origin = fields.Char('补货来源')

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