Compare commits

..

435 Commits

Author SHA1 Message Date
禹翔辉
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
禹翔辉
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
yuxianghui
bc85c457ad 功能刀具添加状态变更跟踪 2025-04-15 16:46:49 +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
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
胡尧
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
黄焱
80f259651c Accept Merge Request #1986: (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/1986?initial=true
2025-04-02 09:05:31 +08:00
hyyy
1c57ee0be1 修复没加引号字段 2025-04-02 09:04:38 +08:00
黄焱
c732bbad62 Accept Merge Request #1984: (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/1984?initial=true
2025-04-02 08:54:03 +08:00
马广威
5285fcd066 Accept Merge Request #1985: (feature/制造功能优化 -> develop)
Merge Request: 零件名称调为可写

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1985?initial=true
2025-04-02 08:50:06 +08:00
mgw
d318d8cb32 零件名称调为可写 2025-04-02 08:49:24 +08:00
hyyy
5b9dc05653 还原代码 2025-04-02 08:48:27 +08:00
hyyy
a513592b21 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/前端样式修改 2025-04-02 08:36:23 +08:00
胡尧
e686ea9469 Accept Merge Request #1983: (feature/commercially_launched -> develop)
Merge Request: Merge branch 'develop' into feature/commercially_launched

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1983
2025-04-01 20:45:15 +08:00
胡尧
651918c51c Merge branch 'develop' into feature/commercially_launched 2025-04-01 20:42:36 +08:00
胡尧
7b13dfcc0e Accept Merge Request #1982: (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/1982
2025-04-01 20:32:39 +08:00
liaodanlong
60e139d5e0 采购申请添加字段的翻译 2025-04-01 16:59:40 +08:00
胡尧
8d5ea0ae19 去掉采购申请不同补货组不能合并的检查 2025-04-01 11:07:08 +08:00
胡尧
d7597359ba 处理多个表面工艺,确认供货路线报错的问题 2025-03-31 16:50:50 +08:00
胡尧
f8309bfaba Merge branch 'develop' into feature/commercially_launched 2025-03-31 16:07:11 +08:00
胡尧
7ed756f922 修改报错翻译 2025-03-31 16:06:50 +08:00
禹翔辉
e837b84a50 Accept Merge Request #1981: (feature/调拨验证方法优化 -> develop)
Merge Request: 1、优化调拨单验证方法;2、隐藏请购明细列表视图字段

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1981
2025-03-31 15:57:51 +08:00
yuxianghui
e7cb100ab1 1、优化调拨单验证方法;2、隐藏请购明细列表视图字段 2025-03-31 15:55:37 +08:00
mgw
d00c9dd38c Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-31 13:02:30 +08:00
胡尧
1d857be16a 修改获取编程文件根目录 2025-03-31 09:28:30 +08:00
马广威
5914e4ca6e Accept Merge Request #1980: (feature/制造功能优化 -> develop)
Merge Request: 调整字段显隐

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1980?initial=true
2025-03-31 09:12:35 +08:00
mgw
d96970fb96 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-03-31 09:11:44 +08:00
mgw
0af9064fce 调整字段显隐 2025-03-31 09:11:21 +08:00
胡尧
21148ae74b 屏蔽sf获取编程文件的逻辑 2025-03-31 09:06:36 +08:00
胡尧
1a3590b6b6 屏蔽物流下单的按钮 2025-03-30 15:03:18 +08:00
胡尧
b55c6c1fe7 修改前置三元检测文件根目录 2025-03-30 11:16:40 +08:00
mgw
8c61dcac29 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-28 14:25:58 +08:00
马广威
41d4e9785f Accept Merge Request #1979: (feature/制造功能优化 -> develop)
Merge Request: 调整页脚布局

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1979?initial=true
2025-03-28 14:24:14 +08:00
mgw
5bf86930e9 调整页脚布局 2025-03-28 14:23:41 +08:00
mgw
7ad9885377 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-28 14:05:29 +08:00
mgw
71433c18b7 Merge branch 'feature/commercially_launched' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-28 14:03:21 +08:00
马广威
4b60ad307b Accept Merge Request #1978: (feature/制造功能优化 -> develop)
Merge Request: Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1978?initial=true
2025-03-28 14:02:11 +08:00
mgw
1a5c8e5f56 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-03-28 14:01:17 +08:00
mgw
8b1e12eb9f 质检报告优化 2025-03-28 14:00:55 +08:00
胡尧
dbf2257a88 Merge branch 'feature/commercially_launched' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-28 13:37:37 +08:00
胡尧
f34c01d1b0 解决表面工艺外协调拨单验证报错的问题 2025-03-28 13:37:29 +08:00
廖丹龙
9c73062593 Accept Merge Request #1977: (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/1977?initial=true
2025-03-28 10:10:56 +08:00
liaodanlong
878fef18df 产品加工面部代码还原 2025-03-28 10:03:41 +08:00
liaodanlong
8348c4fc48 Merge branch 'refs/heads/develop' into feature/commercially_launched 2025-03-27 16:42:22 +08:00
廖丹龙
8643fb2385 Accept Merge Request #1976: (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/1976
2025-03-27 16:41:19 +08:00
liaodanlong
77744e75d7 子制造订单零件图号零件名称问题 2025-03-27 16:38:57 +08:00
胡尧
4cbcf08da8 销售单默认筛选供货方式待确认 2025-03-27 16:10:07 +08:00
mgw
af2a589679 添加翻译文件 2025-03-27 15:59:39 +08:00
胡尧
4b6f04aa9d 修改字段显示位置 2025-03-27 15:51:16 +08:00
廖丹龙
4634d43012 Accept Merge Request #1975: (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/1975
2025-03-27 15:23:00 +08:00
liaodanlong
dd6e8b6707 调试信息 2025-03-27 15:21:15 +08:00
mgw
55337815c7 修改dashboard当日完成数据计算逻辑 2025-03-27 10:42:20 +08:00
廖丹龙
0ccb7cb3d1 Accept Merge Request #1974: (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/1974
2025-03-26 17:24:40 +08:00
liaodanlong
d2155b17b4 Merge branch 'refs/heads/develop' into feature/tool_standard_library_process
# Conflicts:
#	sf_manufacturing/models/product_template.py
2025-03-26 17:01:13 +08:00
liaodanlong
11a5217430 子制造订单零件图号与零件名称获取方式修改 2025-03-26 16:57:47 +08:00
管欢
855e0eb1c2 Accept Merge Request #1973: (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/1973
2025-03-26 14:50:07 +08:00
guanhuan
6a70f3b88a 坯料冗余新增描述字段 2025-03-26 14:32:53 +08:00
guanhuan
236158d556 调拨单零件图号信息搜索 2025-03-26 13:35:28 +08:00
胡尧
1a67c5d1e3 Accept Merge Request #1971: (feature/mrp_bug_fixed -> develop)
Merge Request: 采购申请审批完成后状态变更

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1971?initial=true
2025-03-26 09:10:19 +08:00
胡尧
bdb8763fea 采购申请审批完成后状态变更 2025-03-26 09:07:01 +08:00
胡尧
78ed512699 Accept Merge Request #1970: (feature/mrp_bug_fixed -> develop)
Merge Request: 解决供货路线选择报错,采购申请审批状态编程已审批

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1970?initial=true
2025-03-26 08:44:19 +08:00
胡尧
2e74c76e07 解决供货路线选择报错,采购申请审批状态编程已审批 2025-03-26 08:43:18 +08:00
胡尧
40dcd11da8 解决供货路线选择报错 2025-03-26 08:42:08 +08:00
胡尧
f9b40be428 Accept Merge Request #1969: (feature/mrp_bug_fixed -> develop)
Merge Request: 采购申请跳转到采购单,不默认筛选询价单

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1969?initial=true
2025-03-25 17:10:17 +08:00
胡尧
4ca655ad51 采购申请跳转到采购单,不默认筛选询价单 2025-03-25 17:09:22 +08:00
胡尧
a2ed102895 Accept Merge Request #1968: (feature/mrp_bug_fixed -> develop)
Merge Request: 询价单行项目价格判断

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1968?initial=true
2025-03-25 16:27:55 +08:00
胡尧
8ec746858c 询价单行项目价格判断 2025-03-25 16:25:05 +08:00
胡尧
03539c7ed3 询价单行项目价格判断 2025-03-25 16:20:54 +08:00
胡尧
4d38a11c3c 修改询价单判断行项目价格不能为0 2025-03-25 15:58:24 +08:00
mgw
6312bb988a Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-25 15:04:15 +08:00
马广威
2b082a0dd7 Accept Merge Request #1967: (feature/制造功能优化 -> develop)
Merge Request: 调整报告样式;调整样式

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1967?initial=true
2025-03-25 15:03:12 +08:00
mgw
3e9a11dd7b 调整样式 2025-03-25 15:02:26 +08:00
mgw
f011c1efda 调整报告样式 2025-03-25 15:00:18 +08:00
mgw
fe9548a0d1 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-25 14:08:06 +08:00
马广威
ac2d81285e Accept Merge Request #1966: (feature/制造功能优化 -> develop)
Merge Request: 补充下载链接

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1966?initial=true
2025-03-25 14:06:49 +08:00
mgw
73ae2cd5c3 补充下载链接 2025-03-25 14:06:23 +08:00
胡尧
0347eb48e4 销售订单行增加交期字段,销售订单列表默认值修改为供货方式待确认 2025-03-25 13:48:17 +08:00
mgw
0aae15cbce Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-25 13:32:55 +08:00
禹翔辉
d888be06c4 Accept Merge Request #1965: (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/1965
2025-03-25 13:29:48 +08:00
yuxianghui
2275f7a384 制造订单详情页添加对应采购申请跳转链接 2025-03-25 13:28:31 +08:00
mgw
5a7d70fb6b Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-25 13:27:51 +08:00
马广威
1256f5ecb5 Accept Merge Request #1964: (feature/制造功能优化 -> develop)
Merge Request: 修改报告二维码指向

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1964?initial=true
2025-03-25 13:26:49 +08:00
mgw
4dfac9e96f 修改报告二维码指向 2025-03-25 13:26:12 +08:00
mgw
8ea8bf1f48 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-25 10:40:48 +08:00
马广威
29f143a547 Accept Merge Request #1963: (feature/制造功能优化 -> develop)
Merge Request: 恢复旧报告名称字段

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1963?initial=true
2025-03-25 10:39:48 +08:00
mgw
2eaf8aef8f 恢复旧报告名称字段 2025-03-25 10:39:24 +08:00
liaodanlong
df78019226 Merge remote-tracking branch 'origin/feature/commercially_launched' into feature/commercially_launched 2025-03-25 10:39:22 +08:00
liaodanlong
6ad945b720 产品加工面板空值处理 2025-03-25 10:39:02 +08:00
mgw
3285d4da57 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-25 10:35:00 +08:00
马广威
8d1466485a Accept Merge Request #1962: (feature/制造功能优化 -> develop)
Merge Request: 调整报告编号

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1962?initial=true
2025-03-25 10:33:14 +08:00
mgw
b39281d057 调整报告编号 2025-03-25 10:32:12 +08:00
liaodanlong
60539462a0 报错处理 2025-03-25 10:02:39 +08:00
mgw
fd9018a4c8 调整完成逻辑取值 2025-03-25 09:56:40 +08:00
liaodanlong
81b425ae0c 调拨单零件图号零件名称处理 2025-03-24 17:28:16 +08:00
mgw
f8e8615dc8 适配索引 2025-03-24 17:25:23 +08:00
mgw
2b2da79e33 调整筛选逻辑 2025-03-24 17:18:19 +08:00
马广威
01ac8a19b6 Accept Merge Request #1961: (feature/制造功能优化 -> develop)
Merge Request: 优化数采查询代码

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1961?initial=true
2025-03-24 16:51:02 +08:00
mgw
20a8ca6146 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-03-24 16:50:20 +08:00
mgw
62cd60ec06 优化数采查询代码 2025-03-24 16:49:49 +08:00
liaodanlong
33fabc068a 报错处理 2025-03-24 16:06:11 +08:00
liaodanlong
db4dd33709 报错处理 2025-03-24 15:54:59 +08:00
liaodanlong
4acb0fa0ba Merge branch 'refs/heads/develop' into feature/commercially_launched 2025-03-24 15:39:26 +08:00
廖丹龙
c9ba3a07bf Accept Merge Request #1960: (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/1960
2025-03-24 15:38:07 +08:00
liaodanlong
89a2260adc 日志修改 2025-03-24 15:16:55 +08:00
胡尧
c5ad94c5f3 Merge branch 'develop' into feature/commercially_launched 2025-03-24 14:56:16 +08:00
廖丹龙
02e0a792d4 Accept Merge Request #1959: (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/1959
2025-03-24 14:55:24 +08:00
liaodanlong
e6d54a3eba 调拨单零件图号零件名称计算逻辑 错误处理 2025-03-24 14:53:33 +08:00
胡尧
43c6686240 Merge branch 'develop' into feature/commercially_launched 2025-03-24 14:29:47 +08:00
廖丹龙
9b73ae26e8 Accept Merge Request #1958: (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/1958
2025-03-24 14:28:44 +08:00
liaodanlong
a0606842e5 过滤错误 2025-03-24 14:26:53 +08:00
liaodanlong
d014ba980b 过滤错误 2025-03-24 14:25:19 +08:00
mgw
5c35eae859 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-24 14:03:21 +08:00
马广威
9df76253de Accept Merge Request #1957: (feature/制造功能优化 -> develop)
Merge Request: 恢复按钮控制

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1957?initial=true
2025-03-24 14:02:16 +08:00
mgw
8ba71ea8af 恢复按钮控制 2025-03-24 14:01:35 +08:00
mgw
fde28bed8a Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-24 13:54:14 +08:00
马广威
8fed12f7bb Accept Merge Request #1956: (feature/制造功能优化 -> develop)
Merge Request: 调整按钮显隐逻辑

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1956?initial=true
2025-03-24 13:52:46 +08:00
mgw
489d7030f4 调整按钮显隐逻辑 2025-03-24 13:52:08 +08:00
mgw
16ae845ad9 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-24 13:41:39 +08:00
马广威
2c1d90ae63 Accept Merge Request #1955: (feature/制造功能优化 -> develop)
Merge Request: Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1955?initial=true
2025-03-24 13:40:20 +08:00
mgw
8e21434fba Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-03-24 13:39:46 +08:00
mgw
956cefbd9f 调整按钮显隐 2025-03-24 13:39:19 +08:00
mgw
85789d137b 修复线上pdf版本报告变形 2025-03-24 13:35:43 +08:00
禹翔辉
6e731cfadc Accept Merge Request #1954: (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/1954
2025-03-24 13:17:57 +08:00
yuxianghui
f51f8bebb2 新增按规格生成的采购申请订单跳过审核环节自动更新为已批准状态 2025-03-24 13:16:02 +08:00
胡尧
e10648ad07 Merge branch 'develop' into feature/commercially_launched 2025-03-24 11:13:48 +08:00
胡尧
2bd18def8f Accept Merge Request #1953: (feature/mrp_bug_fixed -> develop)
Merge Request: 修改采购单需要审批提醒的方式

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1953?initial=true
2025-03-24 11:13:10 +08:00
胡尧
61b2b05367 修改采购单需要审批提醒的方式 2025-03-24 11:11:50 +08:00
mgw
d2b02bb6f7 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-24 11:03:07 +08:00
马广威
e7b4f51736 Accept Merge Request #1952: (feature/制造功能优化 -> develop)
Merge Request: 调整报告样式

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1952
2025-03-24 11:02:08 +08:00
mgw
8832b01408 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-03-24 11:01:11 +08:00
mgw
a5c5b499f1 调整报告样式 2025-03-24 11:00:49 +08:00
胡尧
0e1c44c3ac Merge branch 'develop' into feature/commercially_launched 2025-03-24 09:56:19 +08:00
胡尧
4212ed763b Accept Merge Request #1951: (feature/mrp_bug_fixed -> develop)
Merge Request: 取消物流单屏蔽

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1951?initial=true
2025-03-24 09:54:48 +08:00
胡尧
a6ac46617d 取消物流单屏蔽 2025-03-24 09:54:04 +08:00
胡尧
43ba241b42 增加采购申请翻译 2025-03-24 09:51:05 +08:00
胡尧
5ffbe4c6fc Merge branch 'feature/commercially_launched' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-24 09:00:22 +08:00
胡尧
5ae167c133 去掉取消自动加工原因 2025-03-24 09:00:16 +08:00
mgw
0702e1dd51 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-21 18:26:58 +08:00
马广威
d9d434d994 Accept Merge Request #1950: (feature/制造功能优化 -> develop)
Merge Request: 调整报告样式

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1950?initial=true
2025-03-21 18:26:18 +08:00
mgw
08fa26d9c8 调整报告样式 2025-03-21 18:25:20 +08:00
mgw
e861897527 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-21 18:18:29 +08:00
马广威
4ba3a56794 Accept Merge Request #1949: (feature/制造功能优化 -> develop)
Merge Request: 增加已批准为无异动

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1949?initial=true
2025-03-21 18:17:32 +08:00
mgw
4c995d00d0 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-03-21 18:16:18 +08:00
mgw
30b01b6e2c 增加已批准为无异动 2025-03-21 18:15:37 +08:00
胡尧
19a66d2c81 屏蔽物流下单按钮 2025-03-21 16:44:49 +08:00
禹翔辉
b2b9120577 Accept Merge Request #1948: (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/1948
2025-03-21 16:14:32 +08:00
yuxianghui
ad8f63570d 请购明细添加供货方式搜索 2025-03-21 16:10:30 +08:00
胡尧
3bb909c2ee Merge branch 'develop' into feature/commercially_launched 2025-03-21 15:39:56 +08:00
禹翔辉
e8b22cedea Accept Merge Request #1947: (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/1947
2025-03-21 15:39:00 +08:00
yuxianghui
d202e7ee93 零件名称字段计算方法修改 2025-03-21 15:37:10 +08:00
胡尧
da19b86bf5 Merge branch 'develop' into feature/commercially_launched 2025-03-21 15:29:49 +08:00
胡尧
2876d56803 Accept Merge Request #1946: (feature/mrp_bug_fixed -> develop)
Merge Request: 销售订单自动确认

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1946?initial=true
2025-03-21 15:29:22 +08:00
胡尧
e1fd4c465e 销售订单自动确认 2025-03-21 15:27:11 +08:00
胡尧
dc318769af Merge branch 'develop' into feature/commercially_launched 2025-03-21 15:13:28 +08:00
禹翔辉
31820dfef1 Accept Merge Request #1945: (feature/采购申请字段调整 -> develop)
Merge Request: Merge branch 'feature/采购申请代码搬迁' into feature/采购申请字段调整

Created By: @禹翔辉
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1945
2025-03-21 15:07:48 +08:00
yuxianghui
1cb46bb1e1 Merge branch 'feature/采购申请代码搬迁' into feature/采购申请字段调整 2025-03-21 15:06:29 +08:00
yuxianghui
80e9085e29 采购申请字段调整 2025-03-21 15:05:42 +08:00
mgw
311b95bca5 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-21 13:18:22 +08:00
马广威
d1f8bdb1f4 Accept Merge Request #1944: (feature/制造功能优化 -> develop)
Merge Request: 调整原有质检单图纸获取逻辑

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1944
2025-03-21 13:17:11 +08:00
mgw
08267ba1e6 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-03-21 13:16:18 +08:00
mgw
c6f1bc80a7 调整原有质检单图纸获取逻辑 2025-03-21 13:15:59 +08:00
廖丹龙
384761514b Accept Merge Request #1943: (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/1943
2025-03-21 11:34:57 +08:00
胡尧
80118b61c2 Merge branch 'develop' into feature/commercially_launched 2025-03-21 11:24:02 +08:00
禹翔辉
eab3ba9478 Accept Merge Request #1942: (feature/采购申请代码搬迁 -> develop)
Merge Request: Merge branch 'feature/采购单优化' into feature/采购申请代码搬迁

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1942
2025-03-21 11:23:27 +08:00
yuxianghui
c2be45f204 Merge branch 'feature/采购单优化' into feature/采购申请代码搬迁 2025-03-21 11:21:08 +08:00
yuxianghui
86d2fb1ac2 efms-purchase_request模块新增修改代码搬迁到sf-jikimo_purchase_request模块 2025-03-21 11:20:24 +08:00
禹翔辉
f28d07e58f Accept Merge Request #1941: (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/1941
2025-03-20 17:03:28 +08:00
yuxianghui
7124eebabe 采购单采购类型计算方法添加新判断条件 2025-03-20 17:01:36 +08:00
mgw
94d0c14e1f Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-20 16:23:01 +08:00
马广威
9f3791bd6b Accept Merge Request #1940: (feature/制造功能优化 -> develop)
Merge Request: sf-坯料的获取方式是自加工,生成的坯料制造订单的排程单不在取消列表中

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1940?initial=true
2025-03-20 16:21:05 +08:00
mgw
ae1028972c sf-坯料的获取方式是自加工,生成的坯料制造订单的排程单不在取消列表中 2025-03-20 16:19:55 +08:00
mgw
3f6f9bb709 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-20 15:44:11 +08:00
马广威
b86d15c5cb Accept Merge Request #1939: (feature/制造功能优化 -> develop)
Merge Request: Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1939?initial=true
2025-03-20 15:43:17 +08:00
mgw
52e68ffa4c Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-03-20 15:41:49 +08:00
mgw
f85f614190 质检标准类型是出厂检验报告时可以更换控制方式-需要增加校验 2025-03-20 15:41:10 +08:00
禹翔辉
2da22d5f19 Accept Merge Request #1938: (feature/销售_采购申请优化 -> develop)
Merge Request: 1、新增由销售单生成的采购申请自动确认;2、销售单新增【采购申请】跳转按钮

Created By: @禹翔辉
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1938
2025-03-20 15:37:55 +08:00
mgw
98644a4b57 调整加工订单匹配条件 2025-03-20 15:34:17 +08:00
yuxianghui
6c879e4af3 1、新增由销售单生成的采购申请自动确认;2、销售单新增【采购申请】跳转按钮 2025-03-20 15:20:05 +08:00
liaodanlong
cf060d0d6c 自加工坯料制造订单的调拨单零件图号零件名称赋值 2025-03-20 10:24:34 +08:00
mgw
49a1ec353a Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-20 08:49:47 +08:00
马广威
134c68abc8 Accept Merge Request #1937: (feature/制造功能优化 -> develop)
Merge Request: 取消发布增加二次确认;发布状态隐藏

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1937?initial=true
2025-03-20 08:48:23 +08:00
mgw
d6a7a3c919 取消发布增加二次确认;发布状态隐藏 2025-03-20 08:45:40 +08:00
mgw
b89cfb899b Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-19 15:44:21 +08:00
马广威
db661e76f5 Accept Merge Request #1935: (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/1935
2025-03-19 15:43:09 +08:00
mgw
bf8ff7199e Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-03-19 15:42:20 +08:00
胡尧
0ff0cc5fbd Merge branch 'develop' into feature/commercially_launched 2025-03-19 15:41:51 +08:00
廖丹龙
813939a6c2 Accept Merge Request #1936: (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/1936
2025-03-19 15:41:02 +08:00
liaodanlong
221e25f581 【合金牌号】,【材料应用】字段同步 2025-03-19 15:33:06 +08:00
mgw
e4ed431167 Merge branch 'feature/制造功能优化' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-03-19 15:32:07 +08:00
mgw
1a511b93cf 调整名称及总数量逻辑 2025-03-19 15:31:34 +08:00
hyyy
9093770ce1 修改取消列表,修改出厂检验报告 2025-03-19 14:45:04 +08:00
mgw
37173968fd Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-19 13:18:42 +08:00
马广威
a75e236f1f Accept Merge Request #1934: (feature/制造功能优化 -> develop)
Merge Request: 当调拨单完成后,重新发布才重新写入到加工订单明细

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1934?initial=true
2025-03-19 13:17:50 +08:00
mgw
e1177a44e8 当调拨单完成后,重新发布才重新写入到加工订单明细 2025-03-19 13:17:15 +08:00
mgw
470482b7e2 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-19 13:06:52 +08:00
马广威
27702fe46e Accept Merge Request #1933: (feature/制造功能优化 -> develop)
Merge Request: 调整检验数边界值

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1933?initial=true
2025-03-19 13:06:02 +08:00
mgw
8c793c6ad9 调整检验数边界值 2025-03-19 13:04:19 +08:00
liaodanlong
0b067f9999 原材料的调拨单缺少零件图号零件名称 2025-03-19 12:59:16 +08:00
mgw
5b51cc3de4 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-19 12:55:23 +08:00
马广威
625188d46d Accept Merge Request #1932: (feature/制造功能优化 -> develop)
Merge Request: 修改取消明细状态

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1932?initial=true
2025-03-19 12:54:18 +08:00
mgw
6178ad0f8e 修改取消明细状态 2025-03-19 12:53:40 +08:00
mgw
9e939467e5 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-19 11:35:27 +08:00
马广威
6c89a2c4fb Accept Merge Request #1931: (feature/制造功能优化 -> develop)
Merge Request: 调整名称

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1931?initial=true
2025-03-19 11:34:38 +08:00
mgw
dc5e70c118 调整名称 2025-03-19 11:33:59 +08:00
mgw
b05492615f Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-19 11:23:49 +08:00
马广威
03d6f9a32e Accept Merge Request #1930: (feature/制造功能优化 -> develop)
Merge Request: 调整字段名

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1930?initial=true
2025-03-19 11:22:21 +08:00
mgw
8634c1fc40 调整字段名 2025-03-19 11:21:28 +08:00
mgw
32ed0e9693 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-19 11:18:28 +08:00
马广威
e1c535f907 Accept Merge Request #1929: (feature/制造功能优化 -> develop)
Merge Request: 增加包名映射

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1929
2025-03-19 11:17:38 +08:00
mgw
a556c21196 增加包名映射 2025-03-19 11:16:36 +08:00
mgw
623ebe3ec3 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-19 11:09:16 +08:00
马广威
b1821d3ed6 Accept Merge Request #1928: (feature/制造功能优化 -> develop)
Merge Request: state字段添加ondelete策略

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1928?initial=true
2025-03-19 11:08:34 +08:00
mgw
a8d8bcbcee state字段添加ondelete策略 2025-03-19 11:07:49 +08:00
mgw
da06688571 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-19 11:02:40 +08:00
马广威
e7d8587a32 Accept Merge Request #1927: (feature/制造功能优化 -> develop)
Merge Request: 取消列表增加采购申请明细

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1927?initial=true
2025-03-19 11:01:51 +08:00
mgw
b53ea2e0e9 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-03-19 11:01:21 +08:00
mgw
5b979ffc34 取消列表增加采购申请明细 2025-03-19 11:00:48 +08:00
mgw
c01451336d Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-19 10:24:26 +08:00
马广威
e5fc3dfe62 Accept Merge Request #1926: (feature/制造功能优化 -> develop)
Merge Request: 判断文件是否上传

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1926?initial=true
2025-03-19 10:23:38 +08:00
mgw
08cd1a176b 判断文件是否上传 2025-03-19 10:22:44 +08:00
mgw
2db5068e85 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-19 10:06:52 +08:00
马广威
bab21d7b76 Accept Merge Request #1925: (feature/制造功能优化 -> develop)
Merge Request: 出厂检验报告bug修改

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1925?initial=true
2025-03-19 10:05:59 +08:00
mgw
6bdaad8718 出厂检验报告上传模板后-没有自动新增有数值的列 2025-03-19 10:03:59 +08:00
mgw
94441422cd 出厂检验报告已经发布-还可以编辑页面内容-发布后要禁掉编辑功能 2025-03-19 09:43:23 +08:00
mgw
5f48dad86c 发料出库单质检弹窗发布前校验 2025-03-19 08:44:23 +08:00
mgw
c955953335 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-18 16:51:19 +08:00
马广威
f5bf727b34 Accept Merge Request #1924: (feature/制造功能优化 -> develop)
Merge Request: 修正问题

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1924?initial=true
2025-03-18 16:50:35 +08:00
mgw
c55231555c 修正问题 2025-03-18 16:49:50 +08:00
mgw
9220c4b7c4 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-18 16:42:51 +08:00
马广威
e4606ece75 Accept Merge Request #1923: (feature/制造功能优化 -> develop)
Merge Request: 修正数据类型转换

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1923
2025-03-18 16:42:14 +08:00
mgw
972c06cdc1 修正数据类型转换 2025-03-18 16:41:35 +08:00
mgw
70b21c607e Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-18 16:31:48 +08:00
马广威
f8bfb7ba40 Accept Merge Request #1922: (feature/制造功能优化 -> develop)
Merge Request: 调整报告逻辑

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1922
2025-03-18 16:29:24 +08:00
mgw
b2c3694a23 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-03-18 16:28:33 +08:00
mgw
be0df6e66b 调整报告逻辑 2025-03-18 16:26:39 +08:00
mgw
242f9c9050 总数量问题 2025-03-18 15:58:05 +08:00
胡尧
4fe7300ec0 Merge branch 'develop' into feature/commercially_launched 2025-03-18 15:56:19 +08:00
廖丹龙
307bfa19f2 Accept Merge Request #1921: (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/1921
2025-03-18 15:55:27 +08:00
liaodanlong
83b051e0b9 材料型号同步添加材料代号 2025-03-18 15:51:56 +08:00
liaodanlong
5bbcd8ec23 材料型号同步添加材料代号 2025-03-18 15:46:22 +08:00
mgw
282458c945 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-18 13:45:19 +08:00
马广威
b211ffdff9 Accept Merge Request #1920: (feature/制造功能优化 -> develop)
Merge Request: 修改模板样式

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1920?initial=true
2025-03-18 13:44:28 +08:00
mgw
cb445393ce Merge branch 'feature/制造功能优化' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-03-18 13:43:36 +08:00
hyyy
25ba61607f 修改模板样式 2025-03-18 13:42:42 +08:00
胡尧
f6e87493d3 Merge branch 'develop' into feature/commercially_launched 2025-03-18 11:28:23 +08:00
廖丹龙
8c4c65a00f Accept Merge Request #1919: (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/1919
2025-03-18 11:27:43 +08:00
liaodanlong
3642398724 表面工艺零件图号没有问题 2025-03-18 11:25:46 +08:00
禹翔辉
23bcf13d95 Accept Merge Request #1918: (feature/质检弹窗优化_2 -> develop)
Merge Request: 1、质检弹窗和数量应用弹窗顺序调整

Created By: @禹翔辉
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1918
2025-03-18 11:22:47 +08:00
yuxianghui
6b78cb72b3 1、质检弹窗和数量应用弹窗顺序调整 2025-03-18 11:21:56 +08:00
马广威
fde508c563 Accept Merge Request #1917: (feature/制造功能优化 -> develop)
Merge Request: Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1917?initial=true
2025-03-18 11:05:13 +08:00
mgw
b65682f5c9 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-03-18 11:04:36 +08:00
mgw
bfe9c51d57 处理质检可能存在多个出厂检验报告问题 2025-03-18 11:04:19 +08:00
禹翔辉
e0015f9d6b Accept Merge Request #1916: (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/1916
2025-03-18 10:54:35 +08:00
yuxianghui
24166c2e71 Merge branch 'feature/质检弹窗优化' into feature/质检弹窗优化_1 2025-03-18 10:53:29 +08:00
yuxianghui
8fb90c1c35 调拨验证提示数据信息优化 2025-03-18 10:52:34 +08:00
mgw
e141f0af2c Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-18 10:41:01 +08:00
马广威
f8b5871912 Accept Merge Request #1915: (feature/制造功能优化 -> develop)
Merge Request: 修复模板不能下载问题

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1915?initial=true
2025-03-18 10:40:19 +08:00
mgw
40a7c14b81 修复模板不能下载问题 2025-03-18 10:39:52 +08:00
mgw
4618c83b2f Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-18 09:45:00 +08:00
马广威
dee616fbef Accept Merge Request #1914: (feature/制造功能优化 -> develop)
Merge Request: 修复按钮规则;修复排程单取消

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1914?initial=true
2025-03-18 09:44:20 +08:00
mgw
a2868a2581 修复按钮规则;修复排程单取消 2025-03-18 09:43:48 +08:00
mgw
e89400f04e Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/commercially_launched 2025-03-18 09:18:47 +08:00
禹翔辉
5e5c7e5512 Accept Merge Request #1913: (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/1913
2025-03-17 17:31:43 +08:00
yuxianghui
117d90bd5c 质量检查弹出框顺序修改 2025-03-17 17:30:42 +08:00
廖丹龙
fb19a19b25 Accept Merge Request #1912: (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/1912
2025-03-17 16:50:52 +08:00
liaodanlong
1311669814 材料型号同步修改时赋予默认值 2025-03-17 16:39:49 +08:00
liaodanlong
8347a8b029 零件图号零件名称无数据 问题修复 2025-03-17 16:05:42 +08:00
胡尧
051f8128e9 Merge branch 'develop' into feature/commercially_launched 2025-03-17 15:05:47 +08:00
马广威
2cdad0cec4 Accept Merge Request #1911: (feature/制造功能优化 -> develop)
Merge Request: 恢复报告

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1911
2025-03-17 15:03:48 +08:00
mgw
7df5bea5b1 恢复报告 2025-03-17 15:03:17 +08:00
马广威
40965af4c0 Accept Merge Request #1910: (feature/制造功能优化 -> develop)
Merge Request: 暂时不加载报告

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1910?initial=true
2025-03-17 14:26:09 +08:00
mgw
21876f4b9d 暂时不加载报告 2025-03-17 14:25:40 +08:00
马广威
5a8df0a1ae Accept Merge Request #1909: (feature/制造功能优化 -> develop)
Merge Request: 暂时屏蔽工厂名称

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1909?initial=true
2025-03-17 14:18:43 +08:00
mgw
d280d2b776 暂时屏蔽工厂名称 2025-03-17 14:18:07 +08:00
禹翔辉
323a204a48 Accept Merge Request #1907: (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/1907
2025-03-17 13:52:58 +08:00
禹翔辉
3ce7f1771a Merge branch refs/heads/develop into refs/heads/feature/质量检查弹出框优化 2025-03-17 13:52:03 +08:00
马广威
e1dc2bde7e Accept Merge Request #1908: (feature/制造功能优化 -> develop)
Merge Request: 出厂检验报告

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1908?initial=true
2025-03-17 13:49:25 +08:00
mgw
495f1962df 恢复流程控制 2025-03-17 13:47:56 +08:00
mgw
221eb7c3a1 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化
# Conflicts:
#	quality_control/security/ir.model.access.csv
#	quality_control/wizard/quality_check_wizard.py
#	sf_quality/__manifest__.py
#	sf_quality/models/__init__.py
#	sf_quality/models/stock.py
2025-03-17 13:46:40 +08:00
mgw
af529d21ec 调整报告公开可访问 2025-03-17 13:39:59 +08:00
liaodanlong
0a1b48ed93 零件图号零件名称数据填充 2025-03-17 11:26:42 +08:00
mgw
db881ac594 调整报告格式 2025-03-17 11:26:29 +08:00
mgw
61776ca6a3 完善出厂报告需求功能 2025-03-17 11:14:20 +08:00
yuxianghui
6da31b4f48 质量检查弹出框顺序修改 2025-03-17 10:54:18 +08:00
mgw
84f74ae09f 出库单写入报告到bfm 2025-03-17 09:34:47 +08:00
mgw
561b515242 调整数据类型 2025-03-17 09:12:44 +08:00
mgw
71e79502f9 增加报告写入bfm方法 2025-03-14 19:59:13 +08:00
mgw
67f3c29af3 调整二维码内容 2025-03-14 17:58:22 +08:00
mgw
5d703b8b73 缓存报告附件,并清理旧附件 2025-03-14 17:44:57 +08:00
胡尧
8f61f258b1 解决冲突 2025-03-14 16:08:37 +08:00
胡尧
a864845d2b 解决销售订单form中,tree内显示模型的问题 2025-03-14 16:06:33 +08:00
mgw
7755cc3982 优化接口;质量检查wizard添加发布按钮 2025-03-14 15:45:45 +08:00
管欢
34565c5d79 Accept Merge Request #1906: (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/1906
2025-03-14 15:43:18 +08:00
mgw
0f537a3158 增加文件下载功能 2025-03-14 14:54:13 +08:00
mgw
7f99b4ba8b 记录历史报告名字、增加状态修改 2025-03-14 14:22:25 +08:00
mgw
4138903bff 调整向导提示语逻辑 2025-03-14 14:02:14 +08:00
mgw
4200fdbf3b 限制可上传文件类型 2025-03-14 13:52:54 +08:00
胡尧
a6a2e53111 Merge branch 'develop' into feature/commercially_launched 2025-03-14 13:52:18 +08:00
mgw
214f45850e 增加对新增删除行的控制 2025-03-14 12:58:26 +08:00
mgw
c23aa4de75 调整报告样式 2025-03-14 11:13:50 +08:00
hyyy
f66ff6339d 修改 2025-03-14 10:52:50 +08:00
mgw
f9525beb65 调整发布业务逻辑 2025-03-14 10:44:54 +08:00
mgw
35bcfa6fa1 调整按钮 2025-03-14 09:50:13 +08:00
mgw
7b6dda3d75 重新发布按钮也增加二次确认 2025-03-13 19:17:49 +08:00
mgw
954ff6b848 对发布按钮进行二次确认控制 2025-03-13 19:13:57 +08:00
mgw
af3a2880e8 添加发布前校验数据 2025-03-13 18:55:59 +08:00
mgw
424a496046 调整预览报表model名字 2025-03-13 18:33:26 +08:00
mgw
bd2ba3bb49 添加合格图章 2025-03-13 17:55:13 +08:00
mgw
2d4926f8b7 优化上传逻辑 2025-03-13 17:23:26 +08:00
hyyy
9d84b68525 修改报错问题 2025-03-13 17:21:22 +08:00
guanhuan
3baf3e60e8 修复外协入库单根据图号查询不到 2025-03-13 17:21:05 +08:00
mgw
16dbfc3867 限制测量列数 2025-03-13 17:13:51 +08:00
hyyy
6de31608d1 Merge branch 'feature/制造功能优化' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-03-13 17:07:26 +08:00
hyyy
73ce21ef99 优化,修复bug 2025-03-13 17:07:19 +08:00
mgw
c632926348 调整报表展示 2025-03-13 17:07:07 +08:00
禹翔辉
a421055348 Accept Merge Request #1903: (feature/工单Rfid优化 -> develop)
Merge Request: 1、完成 制造订单内外协调拨单质量检查不通过,点击跳转质量检查页面报错

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1903
2025-03-13 16:47:39 +08:00
yuxianghui
cd114d183b 处理质检单控制方式为非数量时,调拨单验证时存在不合格质检单的提示信息数量问题 2025-03-13 16:45:09 +08:00
mgw
20efa0bdab 去除重复声明 2025-03-13 16:44:03 +08:00
mgw
839b3c981d 配合前端列数修改 2025-03-13 16:39:10 +08:00
廖丹龙
395db2cf5f Accept Merge Request #1904: (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/1904
2025-03-13 16:21:00 +08:00
黄焱
1ca069b149 Accept Merge Request #1905: (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/1905
2025-03-13 16:14:31 +08:00
mgw
061714413c Merge branch 'feature/制造功能优化' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-03-13 16:13:09 +08:00
mgw
f780d47562 发布相关 2025-03-13 16:12:48 +08:00
hyyy
082e25feae 增加添加删除列 2025-03-13 16:10:43 +08:00
liaodanlong
ef5eea1845 删除无用代码 2025-03-13 15:32:54 +08:00
yuxianghui
359eae14cc 1、完成 制造订单内外协调拨单质量检查不通过,点击跳转质量检查页面报错 2025-03-13 15:32:12 +08:00
廖丹龙
528d63123f Accept Merge Request #1901: (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/1901
2025-03-13 15:27:59 +08:00
胡尧
a11329eaf8 Merge branch 'develop' into feature/commercially_launched 2025-03-13 14:51:21 +08:00
胡尧
d571b77915 修改工单新模型显示 2025-03-13 14:50:26 +08:00
禹翔辉
b94adc9704 Accept Merge Request #1902: (feature/工单Rfid优化 -> develop)
Merge Request: Merge branch 'feature/制造工单优化' into feature/工单Rfid优化

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1902
2025-03-13 14:42:06 +08:00
yuxianghui
2568e63712 Merge branch 'feature/制造工单优化' into feature/工单Rfid优化 2025-03-13 14:40:12 +08:00
yuxianghui
7ddcfc6226 1、处理 制造订单内外协调拨单质量检查不通过,点击跳转质量检查页面报错 ;2、处理 工单Rfid绑定解绑是数据不正确问题 2025-03-13 14:39:04 +08:00
liaodanlong
3543531f61 表面工艺添加零件图号零件名称 2025-03-13 14:13:28 +08:00
mgw
04b255d5c6 检测报告发布流程 2025-03-13 14:10:06 +08:00
胡尧
24897f07f8 获取编程单,不传递模型参数 2025-03-13 13:58:13 +08:00
胡尧
53779b89a7 Merge branch 'develop' into feature/commercially_launched 2025-03-13 13:06:26 +08:00
禹翔辉
326dcc8b87 Accept Merge Request #1900: (feature/制造工单优化 -> develop)
Merge Request: 1、处理调拨单二次验证时其他提示信息窗口直接关闭问题;2、处理特殊途径-制造订单内外协调拨单质量检查不通过,点击跳转质量检查页面报错 问题

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1900
2025-03-13 13:05:44 +08:00
yuxianghui
9d042dc61e 1、处理调拨单二次验证时其他提示信息窗口直接关闭问题;2、处理特殊途径-制造订单内外协调拨单质量检查不通过,点击跳转质量检查页面报错 问题 2025-03-13 13:02:22 +08:00
胡尧
a1a94867f0 Merge branch 'develop' into feature/commercially_launched 2025-03-13 11:34:17 +08:00
禹翔辉
7ba415968b Accept Merge Request #1899: (feature/制造工单优化 -> develop)
Merge Request: Merge branch 'feature/功能刀具组装提示优化' into feature/制造工单优化

Created By: @禹翔辉
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1899
2025-03-13 11:13:35 +08:00
yuxianghui
e10483b87b Merge branch 'feature/功能刀具组装提示优化' into feature/制造工单优化 2025-03-13 11:12:06 +08:00
yuxianghui
83699fcae6 1、修改质检提示翻译;2、处理单据合并原单据数据不全问题;3、处理制造订单返工报错 2025-03-13 11:11:22 +08:00
胡尧
0a666f568d 解决待发货获取不到模型名字的问题 2025-03-13 10:52:14 +08:00
廖丹龙
b2b8762138 Accept Merge Request #1898: (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/1898
2025-03-13 10:12:56 +08:00
liaodanlong
7c48e6b186 同一个销售订单的不同产品的坯料获取方式不一样,生成的采购订单问题 2025-03-13 09:49:17 +08:00
胡尧
ad8ec770b6 Merge branch 'develop' into feature/commercially_launched 2025-03-13 09:17:33 +08:00
管欢
e87689da33 Accept Merge Request #1896: (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/1896
2025-03-13 08:48:04 +08:00
胡尧
3cc3c48ab3 Merge branch 'develop' into feature/commercially_launched 2025-03-13 08:47:59 +08:00
禹翔辉
dff0841317 Accept Merge Request #1897: (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/1897
2025-03-13 08:43:43 +08:00
yuxianghui
0d8445e444 处理工单的Rfid扫码绑定解绑问题 2025-03-13 08:36:10 +08:00
胡尧
f5da36a82c 修改销售订单模型显示 2025-03-12 17:26:19 +08:00
胡尧
da489555b0 去掉多余的字段 2025-03-12 17:17:21 +08:00
胡尧
29337bfceb 调整模型显示 2025-03-12 17:05:29 +08:00
mgw
c6ae4d933c 计算总数量 2025-03-12 14:14:21 +08:00
mgw
d577630657 增加报告相关结构 2025-03-12 14:06:03 +08:00
liaodanlong
5b084624df 不送检的质量检查单状态更新:等待、待处理 2025-03-12 13:46:07 +08:00
mgw
5e51ee8db3 控制字段、page显隐逻辑 2025-03-12 12:43:13 +08:00
mgw
de1bdbe18b 准备前端开发用结构 2025-03-12 09:08:41 +08:00
mgw
78738ed8aa 检查文件上传 2025-03-11 16:19:00 +08:00
yuxianghui
8237d04f32 功能刀具组装扫码验证物料提示信息优化 2025-03-11 16:12:04 +08:00
hyyy
ef0d05a29d Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/前端样式修改 2025-03-11 13:29:19 +08:00
mgw
e6e740a6c7 质量检查优化-界面部分 2025-03-11 12:54:59 +08:00
胡尧
b8bec37e15 Merge branch 'develop' into feature/commercially_launched 2025-03-11 09:06:43 +08:00
禹翔辉
003a115084 Accept Merge Request #1895: (feature/调拨单优化 -> develop)
Merge Request: Merge branch 'feature/工单页签优化_1' into feature/调拨单优化

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1895
2025-03-11 08:59:33 +08:00
yuxianghui
9ca7f75ef7 Merge branch 'feature/工单页签优化_1' into feature/调拨单优化 2025-03-11 08:55:59 +08:00
胡尧
7654a60f2f Accept Merge Request #1892: (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/1892#mr-1892-review-228565
2025-03-11 08:55:02 +08:00
mgw
bd43de74dd Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-03-10 17:10:46 +08:00
mgw
2cc7386027 质量检查优化 2025-03-10 17:10:27 +08:00
liaodanlong
4a92e39b0d Merge branch 'refs/heads/develop' into feature/tool_standard_library_process 2025-03-10 16:55:39 +08:00
yuxianghui
34456c3506 完成 验证调拨单时,校验不合格产品,如果存在不合格质检单则给与提示 2025-03-10 16:54:15 +08:00
胡尧
dedc820b50 Merge branch 'develop' into feature/commercially_launched 2025-03-10 16:31:35 +08:00
yuxianghui
dc550d1be5 完成 调拨单取消后,关联取消质量检查单 2025-03-10 15:24:24 +08:00
mgw
1541326dfc 增加排程单取消 2025-03-10 14:56:42 +08:00
guanhuan
0d7f348194 调拨列表采购订单询价单页面优化 2025-03-07 11:29:32 +08:00
胡尧
2ccedc95f2 Merge branch 'feature/mrp_bug_fixed' into feature/commercially_launched 2025-03-06 16:24:36 +08:00
胡尧
307f1b221f Accept Merge Request #1894: (feature/mrp_bug_fixed -> develop)
Merge Request: 成品,坯料复制模板中的采购申请字段模板

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1894?initial=true
2025-03-06 16:03:14 +08:00
胡尧
674af1d11d 成品,坯料复制模板中的采购申请字段模板 2025-03-06 16:02:19 +08:00
胡尧
b276f616e5 成品供应商默认取模板中最后一个 2025-03-06 14:03:31 +08:00
胡尧
cb645aa1b9 增加自动化产线的零件供货路线初始化为自动化产线加工 2025-03-06 13:59:14 +08:00
guanhuan
e12755783c 审批优化 2025-03-06 13:46:18 +08:00
禹翔辉
1215d62e76 Accept Merge Request #1893: (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/1893?initial=true
2025-03-06 13:40:34 +08:00
yuxianghui
00c9ad423c Merge branch 'feature/工单页签优化' into feature/工单页签优化_1 2025-03-06 13:39:27 +08:00
yuxianghui
826d5b1d58 1、优化工单子页签可全量配置处理方法; 2025-03-06 13:37:52 +08:00
liaodanlong
9a67caca74 Merge branch 'refs/heads/develop' into feature/tool_standard_library_process 2025-03-06 10:44:31 +08:00
liaodanlong
9c5ecdfe76 sf 材料型号页面修改字段信息 2025-03-06 10:25:56 +08:00
hyyy
0a65c29286 取消销售订单的下游清单界面优化 2025-03-05 17:24:42 +08:00
胡尧
2409dab8b0 增加非自动化原因 2025-03-05 14:27:41 +08:00
禹翔辉
f2415ae80d Accept Merge Request #1891: (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/1891?initial=true
2025-03-05 11:26:11 +08:00
yuxianghui
7d4314abc7 完成 工单子页签可全量配置 2025-03-05 11:24:48 +08:00
马广威
28a3d52aea Accept Merge Request #1890: (feature/制造功能优化 -> develop)
Merge Request: 人工建立销售单取第一份bom

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1890?initial=true
2025-03-04 11:42:02 +08:00
mgw
7e93586f69 人工建立销售单取第一份bom 2025-03-04 11:40:39 +08:00
胡尧
e8fc38e6ed Merge branch 'develop' into feature/commercially_launched 2025-03-04 09:32:07 +08:00
胡尧
52e585e637 解决冲突 2025-02-20 13:51:24 +08:00
胡尧
05dac9fb0c 调整模型上传的流程 2025-02-20 13:36:05 +08:00
126 changed files with 23043 additions and 501 deletions

View File

@@ -115,9 +115,18 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', {
this.setRequired() this.setRequired()
this.listherHeaderBodyNum() this.listherHeaderBodyNum()
}) })
owl.onPatched(() => { owl.onPatched(() => {
this.listherHeaderBodyNum() this.listherHeaderBodyNum()
}) })
const treeModifiers = this.getFieldModifiers(this.props.archInfo.__rawArch);
if(treeModifiers) {
this.props.merge_key = treeModifiers.merge_key;
this.props.merge_fields = treeModifiers.merge_fields.split(',');
const data = this.setColumns(this.props.merge_key);
owl.onMounted(() => {
this.mergeColumns(this.props.merge_fields, data)
})
}
return this._super(...arguments); return this._super(...arguments);
}, },
setRequired() { setRequired() {
@@ -163,7 +172,78 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', {
} catch (e) { } catch (e) {
console.log(e) console.log(e)
} }
},
setColumns( merge_key) {
merge_key = merge_key.split(',')
const data = this.props.list.records
let sourceIndex = 0;
let sourceValue = merge_key.reduce((acc, key) => {
acc[key] = '';
return acc;
}, {});
data.forEach((item, index) => {
if(!item.colspan) {
item.colspan = 1;
}
const itemValue = merge_key.reduce((acc, key) => {
acc[key] = item.data[key];
return acc;
}, {});
if(JSON.stringify(itemValue) == JSON.stringify(sourceValue)) {
data[sourceIndex].colspan ++ ;
item.colspan = 0;
} else {
sourceIndex = index;
sourceValue = itemValue;
}
})
return data
},
getFieldModifiers(xmlString) {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlString, "text/xml");
// 提取 <tree> 的 modifiers
const treeElement = xmlDoc.querySelector("tree");
const treeModifiers = treeElement.getAttribute("modifiers");
if (treeModifiers) {
const parsedTreeModifiers = JSON.parse(treeModifiers);
console.log("Tree Modifiers:", parsedTreeModifiers);
return parsedTreeModifiers;
}
return null;
},
mergeColumns(merge_fields, data) {
const dom = this.tableRef.el
const thead = $(dom).children('thead')
const tbody = $(dom).children('tbody')
let row_no = 0
tbody.children('tr.o_data_row').each(function () {
const tr = $(this)
const td = tr.children('td')
const index = $(this).index()
const col = data[index].colspan
row_no ++
if(col == 0) {
row_no --
}
td.eq(0).text(row_no).attr('rowspan', col)
if(col == 0) {
td.eq(0).remove()
}
td.each(function () {
if(merge_fields.indexOf($(this).attr('name')) >= 0) {
$(this).attr('rowspan', col)
if(col == 0) {
$(this).remove()
}
}
})
})
} }
}) })
patch(FormLabel.prototype, 'jikimo_frontend.FormLabel', { patch(FormLabel.prototype, 'jikimo_frontend.FormLabel', {
get className() { get className() {
@@ -176,7 +256,6 @@ patch(FormLabel.prototype, 'jikimo_frontend.FormLabel', {
); );
const classes = this.props.className ? [this.props.className] : []; const classes = this.props.className ? [this.props.className] : [];
const otherRequired = filedRequiredList[this.props.fieldName] const otherRequired = filedRequiredList[this.props.fieldName]
if(this.props.fieldInfo?.rawAttrs?.class?.indexOf('custom_required') >= 0 || otherRequired) { if(this.props.fieldInfo?.rawAttrs?.class?.indexOf('custom_required') >= 0 || otherRequired) {
classes.push('custom_required_add') classes.push('custom_required_add')
} }
@@ -193,35 +272,6 @@ patch(FormLabel.prototype, 'jikimo_frontend.FormLabel', {
} }
}) })
// 根据进度条设置水印
// const statusbar_params = {
// '已完工': 'bg-primary',
// '完成': 'bg-primary',
// '采购订单': 'bg-primary',
// '作废': 'bg-danger',
// '封存(报废)': 'bg-danger',
// }
// patch(StatusBarField.prototype, 'jikimo_frontend.StatusBarField', {
// setup() {
// owl.onMounted(this.ribbons);
// return this._super(...arguments);
// },
// ribbons() {
// try {
// const dom = $('.o_form_sheet.position-relative')
// const status = statusbar_params[this.currentName]
// if(status && dom.length) {
// dom.prepend(`<div class="o_widget o_widget_web_ribbon">
// <div class="ribbon ribbon-top-right">
// <span class="bg-opacity-75 ${status}" title="">${this.currentName}</span>
// </div>
// </div>`)
// }
// } catch (e) {
// console.log(e)
// }
// }
// })
$(function () { $(function () {

View File

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

View File

@@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
{
'name': '机企猫 采购申请',
'version': '16.0.1.0.0',
'summary': """ 机企猫 采购申请 """,
'author': '机企猫',
'website': 'https://bfw.jikimo.com',
'category': 'purchase',
'depends': ['sf_manufacturing', 'purchase_request'],
'data': [
'views/sale_order_view.xml',
'views/mrp_production.xml',
'views/purchase_request_view.xml',
'wizard/purchase_request_line_make_purchase_order_view.xml',
],
# 'assets': {
# 'web.assets_backend': [
# 'jikimo_purchase_request/static/src/**/*'
# ],
# },
'application': True,
'installable': True,
'auto_install': False,
'license': 'LGPL-3',
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,7 @@
# -*- coding: utf-8 -*-
from . import product_template
from . import purchase_request
from . import sale_order
from . import mrp_production
from . import purchase_order
from . import stock_rule

View File

@@ -0,0 +1,39 @@
from odoo import fields, models, api, _
class MrpProduction(models.Model):
_inherit = 'mrp.production'
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:
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name)])
if pr_ids:
item.pr_mp_count = len(pr_ids)
else:
item.pr_mp_count = 0
def action_view_pr_mp(self):
"""
采购请求
"""
self.ensure_one()
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name)])
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

View File

@@ -0,0 +1,19 @@
from odoo import models, fields
class ProductTemplate(models.Model):
_inherit = 'product.template'
purchase_request_id = fields.Many2one('purchase.request', string='采购申请')
def no_bom_product_create(self, product_id, item, order_id, route_type, i, finish_product):
""" 创建坯料时,复制采购申请 """
template_id = super(ProductTemplate, self).no_bom_product_create(product_id, item, order_id, route_type, i,
finish_product)
template_id.purchase_request = product_id.purchase_request
return template_id
def copy_template(self, product_template_id):
""" 复制成品模板时,复制采购申请 """
super(ProductTemplate, self).copy_template(product_template_id)
self.purchase_request = product_template_id.purchase_request

View File

@@ -0,0 +1,16 @@
from odoo import api, fields, models, _
class PurchaseOrder(models.Model):
_inherit = 'purchase.order'
state = fields.Selection([
('draft', '询价'),
('sent', '发送询价'),
('to approve', '待批准'),
("approved", "已批准"),
('purchase', '采购订单'),
('done', '完成'),
('cancel', '取消'),
('rejected', '已驳回')
], string='Status', readonly=True, index=True, copy=False, default='draft', tracking=True)

View File

@@ -0,0 +1,100 @@
import re
import ast
from odoo import models, fields, api
class PurchaseRequest(models.Model):
_inherit = 'purchase.request'
_description = '采购申请'
# 为state添加取消状态
state = fields.Selection(
selection_add=[('cancel', '已取消')],
ondelete={'cancel': 'set default'} # 添加 ondelete 策略
)
rule_new_add = fields.Boolean('采购请求为规则创建', default=False, compute='_compute_state', store=True)
@api.depends('state')
def _compute_state(self):
for pr in self:
if pr.state != 'draft' and pr.rule_new_add:
pr.rule_new_add = False
def action_view_purchase_order(self):
action = super(PurchaseRequest, self).action_view_purchase_order()
origin_context = ast.literal_eval(action['context'])
if 'search_default_draft' in origin_context:
origin_context.pop('search_default_draft')
action['context'] = origin_context
return action
class PurchaseRequestLine(models.Model):
_inherit = 'purchase.request.line'
_description = '采购申请明细'
origin = fields.Char(string="Source Document")
part_number = fields.Char('零件图号', store=True, compute='_compute_part_number')
part_name = fields.Char('零件名称', store=True, compute='_compute_part_number')
related_product = fields.Many2one('product.product', string='关联产品',
help='经此产品工艺加工成的成品')
supply_method = fields.Selection([
('automation', "自动化产线加工"),
('manual', "人工线下加工"),
('purchase', "外购"),
('outsourcing', "委外加工"),
], string='供货方式', compute='_compute_supply_method', store=True)
@api.depends('origin')
def _compute_supply_method(self):
for prl in self:
order_ids = []
if not prl.origin:
continue
origin = [origin.replace(' ', '') for origin in prl.origin.split(',')]
if 'S' in prl.origin:
# 原单据是销售订单
order_ids = self.env['sale.order'].sudo().search([('name', 'in', origin)]).ids
elif 'MO' in prl.origin:
# 原单据是制造订单
mp_ids = self.env['mrp.production'].sudo().search([('name', 'in', origin)])
order_ids = [mp_id.sale_order_id.id for mp_id in mp_ids] if mp_ids else []
elif 'WH' in prl.origin:
# 原单据是调拨单
sp_ids = self.env['stock.picking'].sudo().search([('name', 'in', origin)])
order_ids = [sp_id.sale_order_id.id for sp_id in sp_ids] if sp_ids else []
order_line = self.env['sale.order.line'].sudo().search(
[('product_id', '=', prl.product_id.id), ('order_id', 'in', order_ids)])
if order_line:
prl.supply_method = order_line[0].supply_method
else:
prl.supply_method = None
@api.depends('product_id')
def _compute_part_number(self):
for record in self:
if record.part_number and record.part_name:
continue
if record.product_id.categ_id.name == '坯料':
product_name = ''
match = re.search(r'(S\d{5}-\d)', record.product_id.name)
# 如果匹配成功,提取结果
if match:
product_name = match.group(0)
sale_order_name = ''
match_sale = re.search(r'S(\d+)', record.product_id.name)
if match_sale:
sale_order_name = match_sale.group(0)
sale_order = self.env['sale.order'].sudo().search(
[('name', '=', sale_order_name)])
if sale_order:
filtered_order_line = sale_order.order_line.filtered(
lambda order_line: re.search(f'{product_name}$', order_line.product_id.name)
)
record.part_number = filtered_order_line.product_id.part_number
record.part_name = filtered_order_line.product_id.part_name
else:
record.part_number = record.product_id.part_number
record.part_name = record.product_id.part_name

View File

@@ -0,0 +1,50 @@
from odoo import fields, models, api, _
class StatusChange(models.Model):
_inherit = 'sale.order'
# def action_confirm(self):
# res = super(StatusChange, self).action_confirm()
# # 采购申请自动确认
# pr_ids = self.env["purchase.request"].sudo().search(
# [('origin', 'like', self.name), ('rule_new_add', '=', True)])
# if pr_ids:
# pr_ids.write({'need_validation': False})
# pr_ids.write({"state": "approved"})
# return res
purchase_request_purchase_order_count = fields.Integer('采购申请单数量', compute='_compute_purchase_request_count',
store=True)
@api.depends('state')
def _compute_purchase_request_count(self):
for so in self:
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', so.name)])
if pr_ids:
so.purchase_request_purchase_order_count = len(pr_ids)
else:
so.purchase_request_purchase_order_count = 0
def action_view_purchase_request_purchase_orders(self):
"""
采购请求
"""
self.ensure_one()
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name)])
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

View File

@@ -0,0 +1,56 @@
from odoo import api, fields, models
class StockRule(models.Model):
_inherit = "stock.rule"
def create_purchase_request(self, procurement_group):
"""
Create a purchase request containing procurement order product.
"""
procurement = procurement_group[0]
rule = procurement_group[1]
purchase_request_model = self.env["purchase.request"]
purchase_request_line_model = self.env["purchase.request.line"]
cache = {}
pr = self.env["purchase.request"]
domain = rule._make_pr_get_domain(procurement.values)
if domain in cache:
pr = cache[domain]
elif domain:
pr = self.env["purchase.request"].search([dom for dom in domain])
pr = pr[0] if pr else False
cache[domain] = pr
if not pr:
request_data = rule._prepare_purchase_request(
procurement.origin, procurement.values
)
request_data.update({'rule_new_add': True})
pr = purchase_request_model.create(request_data)
cache[domain] = pr
elif (
not pr.origin
or procurement.origin not in pr.origin.split(", ")
and procurement.origin != "/"
):
if pr.origin:
if procurement.origin:
pr.write({"origin": pr.origin + ", " + procurement.origin})
else:
pr.write({"origin": procurement.origin})
# Create Line
request_line_data = rule._prepare_purchase_request_line(pr, procurement)
request_line_data.update({'origin': procurement.origin})
purchase_request_line_model.create(request_line_data)
def _run_buy(self, procurements):
res = super(StockRule, self)._run_buy(procurements)
# 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved
origins = list(set([procurement[0].origin for procurement in procurements]))
for origin in origins:
pr_ids = self.env["purchase.request"].sudo().search(
[('origin', 'like', origin), ('rule_new_add', '=', True), ('state', '=', 'draft')])
if pr_ids:
pr_ids.write({'need_validation': False})
pr_ids.write({"state": "approved", 'need_validation': True, 'rule_new_add': False})
return res

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="mrp_production_inherited_form_purchase_request" model="ir.ui.view">
<field name="name">mrp.production.inherited.form.purchase.request</field>
<field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='action_view_mo_delivery']" position="before">
<button class="oe_stat_button" name="action_view_pr_mp" type="object" icon="fa-credit-card"
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>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,66 @@
<odoo>
<record id="view_purchase_request_form_sf" model="ir.ui.view">
<field name="name">purchase.request.sf.form</field>
<field name="model">purchase.request</field>
<field name="inherit_id" ref="purchase_request.view_purchase_request_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='button_draft']" position="attributes">
<attribute name="string">重置草稿</attribute>
</xpath>
<xpath expr="//field[@name='line_ids']//field[@name='purchased_qty']" position="after">
<field name="supply_method"/>
</xpath>
<xpath expr="//field[@name='line_ids']//field[@name='name']" position="replace">
<field name="related_product"/>
<field name="part_number"/>
<field name="part_name"/>
</xpath>
</field>
</record>
<record id="view_purchase_request_line_tree_sf" model="ir.ui.view">
<field name="name">purchase.request.line.sf.tree</field>
<field name="model">purchase.request.line</field>
<field name="inherit_id" ref="purchase_request.purchase_request_line_tree"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='requested_by']" position="replace">
<field name="supply_method"/>
</xpath>
<xpath expr="//field[@name='assigned_to']" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
<xpath expr="//field[@name='name']" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
<xpath expr="//field[@name='supplier_id']" position="after">
<field name="requested_by" widget="many2one_avatar_user"/>
<field name="assigned_to" widget="many2one_avatar_user" invisible="1"/>
</xpath>
<xpath expr="//field[@name='purchased_qty']" position="attributes">
<attribute name="string">采购数量</attribute>
</xpath>
<xpath expr="//field[@name='purchase_state']" position="attributes">
<attribute name="string">订单状态</attribute>
</xpath>
<xpath expr="//field[@name='product_id']" position="after">
<field name="related_product"/>
<field name="part_number"/>
<field name="part_name" invisible="1"/>
</xpath>
</field>
</record>
<record id="view_purchase_request_line_search_sf" model="ir.ui.view">
<field name="name">purchase.request.line.sf.search</field>
<field name="model">purchase.request.line</field>
<field name="inherit_id" ref="purchase_request.purchase_request_line_search"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='product_id']" position="after">
<field name="supply_method"/>
<field name="related_product"/>
<field name="part_number"/>
<field name="part_name"/>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,19 @@
<odoo>
<record id="sale_order_inherited_form_purchase_request_sf" model="ir.ui.view">
<field name="name">sale.order.inherited.form.purchase.request</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale_purchase.sale_order_inherited_form_purchase"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='action_preview_sale_order']" position="before">
<button class="oe_stat_button" name="action_view_purchase_request_purchase_orders" type="object" icon="fa-credit-card"
groups='purchase.group_purchase_user'
attrs="{'invisible': [('purchase_request_purchase_order_count', '=', 0)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="purchase_request_purchase_order_count"/></span>
<span class="o_stat_text">采购申请</span>
</div>
</button>
</xpath>
</field>
</record>
</odoo>

View File

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

View File

@@ -0,0 +1,108 @@
# Copyright 2018-2019 ForgeFlow, S.L.
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0).
from datetime import datetime
from odoo import _, api, fields, models
from odoo.exceptions import UserError, ValidationError
from odoo.tools import get_lang
class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
_inherit = "purchase.request.line.make.purchase.order"
def make_purchase_order(self):
res = []
purchase_obj = self.env["purchase.order"]
po_line_obj = self.env["purchase.order.line"]
purchase = False
if len(set([item_id.line_id.supply_method for item_id in self.item_ids])) > 1:
raise ValidationError('不同供货方式不可合并创建询价单!')
for item in self.item_ids:
line = item.line_id
if item.product_qty <= 0.0:
raise UserError(_("Enter a positive quantity."))
if self.purchase_order_id:
purchase = self.purchase_order_id
if not purchase:
po_data = self._prepare_purchase_order(
line.request_id.picking_type_id,
line.request_id.group_id,
line.company_id,
line.request_id.origin,
)
purchase = purchase_obj.create(po_data)
# Look for any other PO line in the selected PO with same
# product and UoM to sum quantities instead of creating a new
# po line
domain = self._get_order_line_search_domain(purchase, item)
available_po_lines = po_line_obj.search(domain)
new_pr_line = True
# If Unit of Measure is not set, update from wizard.
if not line.product_uom_id:
line.product_uom_id = item.product_uom_id
# Allocation UoM has to be the same as PR line UoM
alloc_uom = line.product_uom_id
wizard_uom = item.product_uom_id
if available_po_lines and not item.keep_description:
new_pr_line = False
po_line = available_po_lines[0]
po_line.purchase_request_lines = [(4, line.id)]
po_line.move_dest_ids |= line.move_dest_ids
po_line_product_uom_qty = po_line.product_uom._compute_quantity(
po_line.product_uom_qty, alloc_uom
)
wizard_product_uom_qty = wizard_uom._compute_quantity(
item.product_qty, alloc_uom
)
all_qty = min(po_line_product_uom_qty, wizard_product_uom_qty)
self.create_allocation(po_line, line, all_qty, alloc_uom)
else:
po_line_data = self._prepare_purchase_order_line(purchase, item)
if item.keep_description:
po_line_data["name"] = item.name
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
)
wizard_product_uom_qty = wizard_uom._compute_quantity(
item.product_qty, alloc_uom
)
all_qty = min(po_line_product_uom_qty, wizard_product_uom_qty)
self.create_allocation(po_line, line, all_qty, alloc_uom)
self._post_process_po_line(item, po_line, new_pr_line)
res.append(purchase.id)
purchase_requests = self.item_ids.mapped("request_id")
purchase_requests.button_in_progress()
return {
"domain": [("id", "in", res)],
"name": _("RFQ"),
"view_mode": "tree,form",
"res_model": "purchase.order",
"view_id": False,
"context": False,
"type": "ir.actions.act_window",
}
def _check_valid_request_line(self, request_line_ids):
for line in self.env["purchase.request.line"].browse(request_line_ids):
if line.request_id.state not in ["approved", "in_progress"]:
raise UserError(
_("采购申请 %s 未审批或未进行中")
% line.request_id.name
)
super(PurchaseRequestLineMakePurchaseOrder, self)._check_valid_request_line(request_line_ids)
@api.model
def check_group(self, request_lines):
# 去掉合并必须同一采购组的限制
pass
class PurchaseRequestLineMakePurchaseOrderItem(models.TransientModel):
_inherit = "purchase.request.line.make.purchase.order.item"
supply_method = fields.Selection(related='line_id.supply_method', string='供货方式')

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="view_purchase_request_line_make_purchase_order_sf" model="ir.ui.view">
<field name="name">Purchase Request Line Make Purchase Order sf</field>
<field name="model">purchase.request.line.make.purchase.order</field>
<field name="inherit_id" ref="purchase_request.view_purchase_request_line_make_purchase_order"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='item_ids']//field[@name='keep_description']" position="before">
<field name="supply_method"/>
</xpath>
</field>
</record>
</odoo>

View File

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

View File

@@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
{
'name': "机企猫 采购审批流程",
'summary': """
Short (1 phrase/line) summary of the module's purpose, used as
subtitle on modules listing or apps.openerp.com""",
'description': """
Long description of module's purpose
""",
'author': "My Company",
'website': "https://www.yourcompany.com",
# Categories can be used to filter modules in modules listing
# Check https://github.com/odoo/odoo/blob/16.0/odoo/addons/base/data/ir_module_category_data.xml
# for the full list
'category': 'Uncategorized',
'version': '0.1',
# any module necessary for this one to work correctly
'depends': ['purchase_request_tier_validation'],
# always loaded
'data': [
],
}

View File

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

View File

@@ -0,0 +1,24 @@
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
import logging
_logger = logging.getLogger(__name__)
class PurchaseRequest(models.Model):
_inherit = 'purchase.request'
def _validate_tier(self, tiers=False):
res = super(PurchaseRequest, self)._validate_tier(tiers)
# 检查是否所有审批都已通过
all_approved = all(
tier_review.status == 'approved'
for tier_review in self.review_ids
)
if self.review_ids and all_approved: # 确保有审批记录
self.state = 'approved'
return res

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -9,6 +9,8 @@ class jikimo_purchase_tier_validation(models.Model):
_name = 'purchase.order' _name = 'purchase.order'
_inherit = ['purchase.order', 'tier.validation'] _inherit = ['purchase.order', 'tier.validation']
_description = "采购订单" _description = "采购订单"
_state_from = ["draft", "to approve", "rejected"]
_state_to = ["approved", "purchase"]
_tier_validation_buttons_xpath = "/form/header/button[@id='draft_confirm'][1]" _tier_validation_buttons_xpath = "/form/header/button[@id='draft_confirm'][1]"
@@ -20,9 +22,9 @@ class jikimo_purchase_tier_validation(models.Model):
is_upload_contract_file = fields.Boolean(string='是否已上传合同文件', default=False) is_upload_contract_file = fields.Boolean(string='是否已上传合同文件', default=False)
def button_confirm(self): def button_confirm(self):
for record in self: # for record in self:
if record.state in ['to approve']: # if record.need_validation and not record.validation_status == 'validated':
raise ValidationError(_('请先完成审批。')) # raise ValidationError(_('请先完成审批。'))
res = super(jikimo_purchase_tier_validation, self).button_confirm() res = super(jikimo_purchase_tier_validation, self).button_confirm()
for record in self: for record in self:
if record.state == 'approved': if record.state == 'approved':
@@ -68,11 +70,6 @@ class jikimo_purchase_tier_validation(models.Model):
return res return res
def _rejected_tier(self, tiers=False):
res = super(jikimo_purchase_tier_validation, self)._rejected_tier(tiers)
self.state = 'draft'
return res
@api.model @api.model
def _get_under_validation_exceptions(self): def _get_under_validation_exceptions(self):
res = super(jikimo_purchase_tier_validation, self)._get_under_validation_exceptions() res = super(jikimo_purchase_tier_validation, self)._get_under_validation_exceptions()

View File

@@ -9,5 +9,6 @@ class MrpBom(models.Model):
# 成品的供应商从模板中获取 # 成品的供应商从模板中获取
if product_type == 'product': if product_type == 'product':
bom_id.subcontractor_id = product.product_tmpl_id.seller_ids.partner_id.id if product.product_tmpl_id.seller_ids:
bom_id.subcontractor_id = product.product_tmpl_id.seller_ids[-1].partner_id.id
return bom_id return bom_id

View File

@@ -8,7 +8,7 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//notebook/page[last()]" position="after"> <xpath expr="//notebook/page[last()]" position="after">
<field name="routing_type" invisible="1"/> <field name="routing_type" invisible="1"/>
<page string="异常记录" name="workorder_exception" attrs="{'invisible': [('routing_type', '!=', 'CNC加工')]}"> <page string="异常记录" name="workorder_exception" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "ER")]}'>
<field name="exception_ids" nolabel="1" readonly="1"> <field name="exception_ids" nolabel="1" readonly="1">
<tree create="false" delete="false" edit="false"> <tree create="false" delete="false" edit="false">
<field name="exception_content" string="反馈的异常/问题信息"/> <field name="exception_content" string="反馈的异常/问题信息"/>

View File

@@ -4,3 +4,4 @@
from . import models from . import models
from . import wizard from . import wizard
from . import report from . import report
from . import controllers

View File

@@ -8,7 +8,7 @@
'sequence': 120, 'sequence': 120,
'summary': 'Control the quality of your products', 'summary': 'Control the quality of your products',
'website': 'https://www.odoo.com/app/quality', 'website': 'https://www.odoo.com/app/quality',
'depends': ['quality', 'sf_manufacturing'], 'depends': ['quality', 'sf_manufacturing', 'base_import'],
'description': """ 'description': """
Quality Control Quality Control
=============== ===============
@@ -20,12 +20,15 @@ Quality Control
""", """,
'data': [ 'data': [
'data/quality_control_data.xml', 'data/quality_control_data.xml',
'wizard/import_complex_model.xml',
'wizard/quality_wizard_view.xml',
'report/worksheet_custom_reports.xml', 'report/worksheet_custom_reports.xml',
'report/worksheet_custom_report_templates.xml', 'report/worksheet_custom_report_templates.xml',
'views/quality_views.xml', 'views/quality_views.xml',
'views/product_views.xml', 'views/product_views.xml',
'views/stock_move_views.xml', 'views/stock_move_views.xml',
'views/stock_picking_views.xml', 'views/stock_picking_views.xml',
'views/quality.check.measures.line.xml',
'wizard/quality_check_wizard_views.xml', 'wizard/quality_check_wizard_views.xml',
'security/ir.model.access.csv', 'security/ir.model.access.csv',
], ],

View File

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

View File

@@ -0,0 +1,120 @@
# -*- coding: utf-8 -*-
from odoo import http
from odoo.http import request, Response
import base64
import json
class QualityController(http.Controller):
@http.route(['/api/quality/report/download'], type='http', auth='public', csrf=False, website=False) # 移除 cors="*"
def get_quality_report(self, retrospect_ref=None, **kwargs):
"""获取质检报告的下载接口
Args:
retrospect_ref: 追溯码
Returns:
直接返回文件下载响应
"""
try:
# 如果retrospect_ref为None尝试从查询参数获取
if not retrospect_ref:
retrospect_ref = kwargs.get('retrospect_ref')
# 参数验证
if not retrospect_ref:
return self._json_response({
'status': 'error',
'message': '追溯码不能为空'
})
# 查找对应的质检单
quality_check = request.env['quality.check'].sudo().search([
('picking_id.retrospect_ref', '=', retrospect_ref),
('publish_status', '=', 'published') # 只返回已发布的报告
], limit=1)
if not quality_check:
return self._json_response({
'status': 'error',
'message': '未找到对应的质检报告或报告未发布'
})
if not quality_check.report_number_id:
return self._json_response({
'status': 'error',
'message': '质检报告文件不存在'
})
# 获取文件内容
document = quality_check.report_number_id
if not document.raw: # 检查文件内容是否存在
return self._json_response({
'status': 'error',
'message': '文件内容不存在'
})
# 构建文件名(确保有.pdf后缀)
filename = document.name
if not filename.lower().endswith('.pdf'):
filename = f"{filename}.pdf"
# 返回文件下载响应
return Response(
document.raw,
headers=[
('Content-Type', 'application/pdf'),
('Content-Disposition', f'attachment; filename="{filename}"'),
('Access-Control-Allow-Origin', '*'),
('Access-Control-Allow-Methods', 'GET, OPTIONS'),
('Access-Control-Allow-Headers', 'Content-Type, Authorization')
]
)
except Exception as e:
return self._json_response({
'status': 'error',
'message': f'系统错误: {str(e)}'
})
def _json_response(self, data):
"""返回JSON格式的响应"""
return Response(
json.dumps(data, ensure_ascii=False),
mimetype='application/json;charset=utf-8',
headers=[
('Access-Control-Allow-Origin', '*'),
('Access-Control-Allow-Methods', 'GET, OPTIONS'),
('Access-Control-Allow-Headers', 'Content-Type, Authorization')
]
)
class QualityReportController(http.Controller):
@http.route('/quality/report/<int:document_id>', type='http', auth='public')
def get_public_report(self, document_id, **kw):
"""提供公开访问PDF报告的控制器"""
document = request.env['documents.document'].sudo().browse(int(document_id))
# 安全检查:确保只有质检报告文档可以被访问
if document.exists() and document.res_model == 'quality.check':
# 获取PDF内容
pdf_content = document.raw
# 返回PDF内容
return request.make_response(
pdf_content,
headers=[
('Content-Type', 'application/pdf'),
('Content-Disposition', f'inline; filename={document.name}.pdf')
]
)
return request.not_found()
@http.route('/quality/report/not_published', type='http', auth='public')
def get_not_published_report(self, **kw):
"""提供未发布报告的控制器"""
return "报告尚未发布"

View File

@@ -10,6 +10,12 @@ from odoo import api, models, fields, _
from odoo.api import depends from odoo.api import depends
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_round from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_round
from odoo.osv.expression import OR from odoo.osv.expression import OR
from odoo.exceptions import UserError
from odoo.tools import image_data_uri
from base64 import b64encode
import requests
import json
import base64
class QualityPoint(models.Model): class QualityPoint(models.Model):
@@ -34,7 +40,8 @@ class QualityPoint(models.Model):
('day', 'Days'), ('day', 'Days'),
('week', 'Weeks'), ('week', 'Weeks'),
('month', 'Months')], default="day") # TDE RENAME ? ('month', 'Months')], default="day") # TDE RENAME ?
is_lot_tested_fractionally = fields.Boolean(string="Lot Tested Fractionally", help="Determines if only a fraction of the lot should be tested") is_lot_tested_fractionally = fields.Boolean(string="Lot Tested Fractionally",
help="Determines if only a fraction of the lot should be tested")
testing_percentage_within_lot = fields.Float(help="Defines the percentage within a lot that should be tested") testing_percentage_within_lot = fields.Float(help="Defines the percentage within a lot that should be tested")
norm = fields.Float('Norm', digits='Quality Tests') # TDE RENAME ? norm = fields.Float('Norm', digits='Quality Tests') # TDE RENAME ?
tolerance_min = fields.Float('Min Tolerance', digits='Quality Tests') tolerance_min = fields.Float('Min Tolerance', digits='Quality Tests')
@@ -63,7 +70,7 @@ class QualityPoint(models.Model):
if n > 1: if n > 1:
point.average = mean point.average = mean
point.standard_deviation = sqrt( s / ( n - 1)) point.standard_deviation = sqrt(s / (n - 1))
elif n == 1: elif n == 1:
point.average = mean point.average = mean
point.standard_deviation = 0.0 point.standard_deviation = 0.0
@@ -94,7 +101,7 @@ class QualityPoint(models.Model):
checks = self.env['quality.check'].search([ checks = self.env['quality.check'].search([
('point_id', '=', self.id), ('point_id', '=', self.id),
('create_date', '>=', date_previous.strftime(DEFAULT_SERVER_DATETIME_FORMAT))], limit=1) ('create_date', '>=', date_previous.strftime(DEFAULT_SERVER_DATETIME_FORMAT))], limit=1)
return not(bool(checks)) return not (bool(checks))
return super(QualityPoint, self).check_execute_now() return super(QualityPoint, self).check_execute_now()
def _get_type_default_domain(self): def _get_type_default_domain(self):
@@ -123,13 +130,491 @@ class QualityPoint(models.Model):
class QualityCheck(models.Model): class QualityCheck(models.Model):
_inherit = "quality.check" _inherit = "quality.check"
part_name = fields.Char('零件名称', compute='_compute_part_name_number', readonly=True) part_name = fields.Char('零件名称', related='product_id.part_name', readonly=False, store=True)
part_number = fields.Char('零件图号', compute='_compute_part_name_number', readonly=True) part_number = fields.Char('零件图号', related='product_id.part_number', readonly=False, store=True)
@depends('product_id') material_name = fields.Char('材料名称', compute='_compute_material_name')
def _compute_part_name_number(self):
# # 总数量值为调拨单_产品明细_数量
# total_qty = fields.Float('总数量', compute='_compute_total_qty', readonly=True)
# # 检验数
# check_qty = fields.Float('检验数', compute='_compute_check_qty', readonly=True)
# # 出厂检验报告编号
# report_number = fields.Char('出厂检验报告编号', compute='_compute_report_number', readonly=True)
# 总数量值为调拨单_产品明细_数量
total_qty = fields.Char('总数量', compute='_compute_total_qty')
column_nums = fields.Integer('测量值列数', default=1)
@api.depends('picking_id')
def _compute_total_qty(self):
for record in self: for record in self:
record.part_number = record.product_id.part_number if record.picking_id:
record.part_name = record.product_id.part_name total_qty = 0
for move in record.picking_id.move_ids_without_package:
if move.product_id == record.product_id:
total_qty = int(move.product_uom_qty)
record.total_qty = total_qty if total_qty > 0 else ''
else:
record.total_qty = ''
# 检验数
check_qty = fields.Integer('检验数', default=lambda self: self._get_default_check_qty())
def _get_default_check_qty(self):
"""根据条件设置检验数的默认值"""
# 这里需要使用_origin来获取已存储的记录因为新记录在创建时可能还没有这些值
if self._origin:
if self._origin.measure_on == 'product' and self._origin.test_type_id.name == '出厂检验报告':
return ''
elif self._origin.measure_on == 'product':
return '1'
return ''
@api.onchange('test_type_id', 'measure_on')
def _onchange_check_qty(self):
"""当测试类型或测量对象变化时,更新检验数"""
if self.measure_on == 'product' and self.test_type_id.name == '出厂检验报告':
self.check_qty = 0
elif self.measure_on == 'product':
self.check_qty = 1
# 出厂检验报告编号
report_number_id = fields.Many2one('documents.document', string='出厂检验报告编号', readonly=True)
report_number_name = fields.Char('出厂检验报告编号名称', compute='_compute_report_number_name')
old_report_name = fields.Char('旧出厂检验报告编号', default='')
@api.depends('serial_number', 'part_number')
def _compute_report_number_name(self):
for record in self:
str_serial_number = '0' + str(record.serial_number) if record.serial_number < 10 else str(
record.serial_number)
str_part_number = record.part_number if record.part_number else ''
record.report_number_name = f'FQC{str_part_number}{str_serial_number}'
# 出厂检验报告、关联文档的数据
report_content = fields.Binary(string='出厂检验报告', related='report_number_id.datas')
is_out_check = fields.Boolean(string='是否出库检验', compute='_compute_is_out_check', readonly=True)
measure_line_ids = fields.One2many('quality.check.measure.line', 'check_id', string='测量明细')
categ_type = fields.Selection(string='产品的类别', related='product_id.categ_id.type', store=True)
report_result = fields.Selection([
('OK', 'OK'),
('NG', 'NG')
], string='出厂检验报告结果', default='OK')
measure_operator = fields.Many2one('res.users', string='操机员')
quality_manager = fields.Many2one('res.users', string='质检员', compute='_compute_quality_manager')
@api.depends('measure_line_ids')
def _compute_quality_manager(self):
for record in self:
if record.measure_line_ids:
record.quality_manager = record.env.user.id
else:
record.quality_manager = False
# 流水号(从1开始最大99)
serial_number = fields.Integer('流水号', default=1, readonly=True)
# 发布历史
report_history_ids = fields.One2many('quality.check.report.history', 'check_id', string='发布历史')
# 发布状态
publish_status = fields.Selection([
('draft', '草稿'),
('published', '已发布'),
('canceled', '已撤销')
], string='发布状态', default='draft')
# 出厂检验报告是否已上传
is_factory_report_uploaded = fields.Boolean(string='出厂检验报告是否已上传', default=False)
def add_measure_line(self):
"""
新增测量值,如果测量值有5列了则提示“最多只能有5列测量值”
"""
if self.column_nums >= 5:
raise UserError(_('最多只能有5列测量值'))
else:
for line in self.measure_line_ids:
field_name = f'measure_value{self.column_nums + 1}'
if hasattr(line, field_name):
line[field_name] = False
self.column_nums = self.column_nums + 1
def remove_measure_line(self):
"""
删除测量值
"""
if self.column_nums <= 1:
raise UserError(_('最少要有1列测量值'))
else:
for line in self.measure_line_ids:
field_name = f'measure_value{self.column_nums}'
if hasattr(line, field_name):
line[field_name] = False
self.column_nums = self.column_nums - 1
def do_preview(self):
"""
预览出厂检验报告
"""
pass
def do_publish(self):
"""发布出厂检验报告"""
self.ensure_one()
self._check_part_number()
self._check_measure_line()
self._check_check_qty_and_total_qty()
# 打开确认向导而不是直接发布
return {
'name': _('发布确认'),
'type': 'ir.actions.act_window',
'res_model': 'quality.check.publish.wizard',
'view_mode': 'form',
'target': 'new',
'context': {
'default_check_id': self.id,
'default_product_name': self.product_id.name,
'default_total_qty': self.total_qty,
'default_check_qty': self.check_qty,
'default_measure_count': self.column_nums,
'default_item_count': len(self.measure_line_ids),
'default_old_report_name': self.old_report_name,
'default_publish_status': self.publish_status,
}
}
def _do_publish_implementation(self):
"""实际执行发布操作的方法"""
self.ensure_one()
# 1. 获取已发布的文档文件夹
workspace = self.env['documents.folder'].search(
[('parent_folder_id', '=', self.env.ref('sf_quality.documents_purchase_contracts_folder').id),
('name', '=', '已发布')], limit=1)
if self.serial_number > 99:
raise UserError(_('流水号不能大于99'))
# 2. 先创建空文档记录
doc_vals = {
'name': self.report_number_name,
'mimetype': 'application/pdf',
'res_id': self.id,
'folder_id': workspace.id,
'res_model': self._name,
}
doc = self.env['documents.document'].create(doc_vals)
# 3. 关联文档到质检记录
self.write({
'report_number_id': doc.id,
'quality_state': 'pass'
})
# 4. 获取报告动作并生成PDF此时二维码将包含正确的文档ID
report_action = self.env.ref('sf_quality.action_report_quality_inspection')
pdf_content, _ = report_action._render_qweb_pdf(
report_ref=report_action.report_name,
res_ids=self.ids
)
# 5. 更新文档内容
doc.write({
'raw': pdf_content
})
# 6. 记录发布历史
self.env['quality.check.report.history'].create({
'check_id': self.id,
'report_number_id': doc.id,
'action': 'publish',
'operator': self.env.user.name,
'operation_time': datetime.now(),
'document_status': 'published',
'sequence': len(self.report_history_ids) + 1
})
# 7. 更新其他信息
self.serial_number += 1
if self.publish_status == 'canceled' and self.picking_id.state == 'done':
self.upload_factory_report()
self.write({
'publish_status': 'published',
})
return True
# 发布前检验零件图号、操机员、质检员
def _check_part_number(self):
if not self.part_number:
raise UserError(_('零件图号不能为空'))
if not self.measure_operator:
raise UserError(_('操机员不能为空'))
# 发布前校验明细行列均非空
def _check_measure_line(self):
for record in self:
if not record.measure_line_ids:
raise UserError(_('请先添加测量明细'))
for line in record.measure_line_ids:
if not line.measure_item:
raise UserError(_('有检测项目值为空'))
for i in range(1, record.column_nums + 1):
if not getattr(line, f'measure_value{i}'):
raise UserError(_('有测量值为空'))
# 发布前校验检验数与总数量、检验数与测量件数(即测量列数)
def _check_check_qty_and_total_qty(self):
for record in self:
if not record.check_qty:
raise UserError(_('请先输入检验数'))
if not record.total_qty:
raise UserError(_('总数量不能为空'))
if record.check_qty > int(record.total_qty):
raise UserError(_('检验数不可超过总数量'))
if record.column_nums > record.check_qty:
raise UserError(_('测量件数不可超过检验数'))
def do_cancel_publish(self):
"""
取消发布出厂检验报告(将当前质检单关联的出厂检验报告文档位置移动到废弃文件夹), 并记录发布历史
"""
self.ensure_one()
# 1. 获取已发布的文档文件夹
workspace = self.env['documents.folder'].search(
[('parent_folder_id', '=', self.env.ref('sf_quality.documents_purchase_contracts_folder').id),
('name', '=', '已发布')], limit=1)
# 2. 将当前质检单关联的出厂检验报告文档位置移动到废弃文件夹
self.report_number_id.write({
'folder_id': self.env.ref('sf_quality.documents_purchase_contracts_folder_canceled').id,
})
# 3. 记录发布历史
self.env['quality.check.report.history'].create({
'check_id': self.id,
'report_number_id': self.report_number_id.id,
'action': 'cancel_publish',
'operator': self.env.user.name,
'operation_time': datetime.now(),
'document_status': 'canceled',
'sequence': len(self.report_history_ids) + 1
})
self.write({
'old_report_name': self.report_number_id.name
})
# 3. 更新发布状态
self.write({
'publish_status': 'canceled',
'report_number_id': False,
'quality_state': 'none'
})
if self.is_factory_report_uploaded:
# 4. 删除加工订单明细中的出厂检验报告
self.delete_factory_report()
return True
def do_re_publish(self):
"""
重新发布出厂检验报告,参考发布规则
"""
return self.do_publish()
def generate_qr_code(self):
"""生成二维码URL"""
self.ensure_one()
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
return image_data_uri(
b64encode(self.env['ir.actions.report'].barcode(
'QR', base_url + '/#/index/publicPay?order_id=' + str(self.id) + '&source=%2Findex%2Fmyorder',
width=140, height=140)
)
)
def get_latest_report_attachment(self, check_id):
"""获取指定质检记录的最新报告附件,并删除旧的报告附件"""
# 查找特定质检记录的所有附件
attachments = self.env['ir.attachment'].search([
('res_model', '=', 'quality.check'),
('res_id', '=', check_id),
('name', 'like', 'QC-QC') # 根据您的命名规则调整
], order='create_date DESC') # 按创建日期降序排序
# # 如果附件数量大于1则删除除最新报告外的其他报告附件
# if len(attachments) > 1:
# for attachment in attachments[1:]:
# attachment.unlink()
# 返回最新的附件(如果存在)
return attachments and attachments[0] or False
def get_report_url(self):
"""生成报告访问URL"""
self.ensure_one()
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
if self.report_number_id:
print(f"{base_url}/quality/report/{self.report_number_id.id}")
return f"{base_url}/quality/report/{self.report_number_id.id}"
else:
return f"{base_url}/quality/report/not_published"
def upload_factory_report(self):
"""
上传出厂检验报告到加工订单明细中
将当前质检单的出厂检验报告上传到对应的加工订单明细中
"""
self.ensure_one()
if not self.report_content:
raise UserError(_('当前质检单没有出厂检验报告,请先发布报告'))
if not self.product_id.model_name:
raise UserError(_('产品模型名称为空'))
if not self.picking_id or not self.picking_id.origin:
raise UserError(_('无法找到相关的调拨单或来源单据'))
# 获取订单号(从调拨单的来源字段获取)
order_ref = self.picking_id.retrospect_ref
try:
# 准备请求数据
payload = {
"order_ref": order_ref,
"model_name": self.product_id.model_name,
"report_file": self.report_content.decode('utf-8') if isinstance(self.report_content,
bytes) else self.report_content
}
# 将Python字典转换为JSON字符串
json_data = json.dumps(payload)
# 获取服务器URL
base_url = self.env['ir.config_parameter'].sudo().get_param('bfm_url_new')
api_url = f"{base_url}/api/report/create"
# 设置请求头
headers = {
'Content-Type': 'application/json',
}
# 发送POST请求
response = requests.post(api_url, data=json_data, headers=headers)
# 处理响应
if response.status_code == 200:
result = response.json()
if result.get('success'):
# 上传成功,显示成功消息
self.is_factory_report_uploaded = True
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('上传成功'),
'message': _('出厂检验报告已成功上传到加工订单明细'),
'type': 'success',
'sticky': False,
}
}
else:
# API返回失败信息
raise UserError(_('上传失败: %s') % result.get('message', '未知错误'))
else:
# HTTP请求失败
raise UserError(_('请求失败,状态码: %s') % response.status_code)
except Exception as e:
raise UserError(_('上传过程中发生错误: %s') % str(e))
def delete_factory_report(self):
"""
删除加工订单明细中的出厂检验报告
"""
# 获取订单号(从调拨单的来源字段获取)
order_ref = self.picking_id.retrospect_ref
if not order_ref:
raise UserError(_('无法找到相关的调拨单或来源单据'))
if not self.product_id.model_name:
raise UserError(_('产品模型名称为空'))
try:
# 准备请求数据
payload = {
"order_ref": order_ref,
"model_name": self.product_id.model_name
}
# 将Python字典转换为JSON字符串
json_data = json.dumps(payload)
# 获取服务器URL
base_url = self.env['ir.config_parameter'].sudo().get_param('bfm_url_new')
api_url = f"{base_url}/api/report/delete"
# 设置请求头
headers = {
'Content-Type': 'application/json',
}
# 发送POST请求
response = requests.post(api_url, data=json_data, headers=headers)
# 处理响应
if response.status_code == 200:
result = response.json()
if result.get('success'):
# 删除成功,显示成功消息
self.is_factory_report_uploaded = False
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('删除成功'),
'message': _('出厂检验报告已成功删除'),
'type': 'success',
'sticky': False,
}
}
else:
# API返回失败信息
raise UserError(_('删除失败: %s') % result.get('message', '未知错误'))
else:
# HTTP请求失败
raise UserError(_('请求失败,状态码: %s') % response.status_code)
except Exception as e:
raise UserError(_('删除过程中发生错误: %s') % str(e))
@depends('product_id')
def _compute_material_name(self):
for record in self:
materials_id_name = record.product_id.materials_id.name if record.product_id.materials_id else ''
materials_type_name = record.product_id.materials_type_id.name if record.product_id.materials_type_id else ''
record.material_name = materials_id_name + ' ' + materials_type_name
@depends('test_type_id')
def _compute_is_out_check(self):
for record in self:
if record.test_type_id.name == '出厂检验报告':
record.is_out_check = True
else:
record.is_out_check = False
failure_message = fields.Html(related='point_id.failure_message', readonly=True) failure_message = fields.Html(related='point_id.failure_message', readonly=True)
measure = fields.Float('Measure', default=0.0, digits='Quality Tests', tracking=True) measure = fields.Float('Measure', default=0.0, digits='Quality Tests', tracking=True)
measure_success = fields.Selection([ measure_success = fields.Selection([
@@ -141,7 +626,8 @@ class QualityCheck(models.Model):
tolerance_max = fields.Float('Max Tolerance', related='point_id.tolerance_max', readonly=True) tolerance_max = fields.Float('Max Tolerance', related='point_id.tolerance_max', readonly=True)
warning_message = fields.Text(compute='_compute_warning_message') warning_message = fields.Text(compute='_compute_warning_message')
norm_unit = fields.Char(related='point_id.norm_unit', readonly=True) norm_unit = fields.Char(related='point_id.norm_unit', readonly=True)
qty_to_test = fields.Float(compute="_compute_qty_to_test", string="Quantity to Test", help="Quantity of product to test within the lot") qty_to_test = fields.Float(compute="_compute_qty_to_test", string="Quantity to Test",
help="Quantity of product to test within the lot")
qty_tested = fields.Float(string="Quantity Tested", help="Quantity of product tested within the lot") qty_tested = fields.Float(string="Quantity Tested", help="Quantity of product tested within the lot")
measure_on = fields.Selection([ measure_on = fields.Selection([
('operation', 'Operation'), ('operation', 'Operation'),
@@ -150,7 +636,8 @@ class QualityCheck(models.Model):
help="""Operation = One quality check is requested at the operation level. help="""Operation = One quality check is requested at the operation level.
Product = A quality check is requested per product. Product = A quality check is requested per product.
Quantity = A quality check is requested for each new product quantity registered, with partial quantity checks also possible.""") Quantity = A quality check is requested for each new product quantity registered, with partial quantity checks also possible.""")
move_line_id = fields.Many2one('stock.move.line', 'Stock Move Line', check_company=True, help="In case of Quality Check by Quantity, Move Line on which the Quality Check applies") move_line_id = fields.Many2one('stock.move.line', 'Stock Move Line', check_company=True,
help="In case of Quality Check by Quantity, Move Line on which the Quality Check applies")
lot_name = fields.Char('Lot/Serial Number Name') lot_name = fields.Char('Lot/Serial Number Name')
lot_line_id = fields.Many2one('stock.lot', store=True, compute='_compute_lot_line_id') lot_line_id = fields.Many2one('stock.lot', store=True, compute='_compute_lot_line_id')
qty_line = fields.Float(compute='_compute_qty_line', string="Quantity") qty_line = fields.Float(compute='_compute_qty_line', string="Quantity")
@@ -231,7 +718,8 @@ class QualityCheck(models.Model):
def _compute_qty_to_test(self): def _compute_qty_to_test(self):
for qc in self: for qc in self:
if qc.is_lot_tested_fractionally: if qc.is_lot_tested_fractionally:
qc.qty_to_test = float_round(qc.qty_line * qc.testing_percentage_within_lot / 100, precision_rounding=self.product_id.uom_id.rounding, rounding_method="UP") qc.qty_to_test = float_round(qc.qty_line * qc.testing_percentage_within_lot / 100,
precision_rounding=self.product_id.uom_id.rounding, rounding_method="UP")
else: else:
qc.qty_to_test = qc.qty_line qc.qty_to_test = qc.qty_line
@@ -386,7 +874,8 @@ class QualityAlert(models.Model):
class ProductTemplate(models.Model): class ProductTemplate(models.Model):
_inherit = "product.template" _inherit = "product.template"
quality_control_point_qty = fields.Integer(compute='_compute_quality_check_qty', groups='quality.group_quality_user') quality_control_point_qty = fields.Integer(compute='_compute_quality_check_qty',
groups='quality.group_quality_user')
quality_pass_qty = fields.Integer(compute='_compute_quality_check_qty', groups='quality.group_quality_user') quality_pass_qty = fields.Integer(compute='_compute_quality_check_qty', groups='quality.group_quality_user')
quality_fail_qty = fields.Integer(compute='_compute_quality_check_qty', groups='quality.group_quality_user') quality_fail_qty = fields.Integer(compute='_compute_quality_check_qty', groups='quality.group_quality_user')
@@ -394,14 +883,16 @@ class ProductTemplate(models.Model):
def _compute_quality_check_qty(self): def _compute_quality_check_qty(self):
for product_tmpl in self: for product_tmpl in self:
product_tmpl.quality_fail_qty, product_tmpl.quality_pass_qty = product_tmpl.product_variant_ids._count_quality_checks() product_tmpl.quality_fail_qty, product_tmpl.quality_pass_qty = product_tmpl.product_variant_ids._count_quality_checks()
product_tmpl.quality_control_point_qty = product_tmpl.with_context(active_test=product_tmpl.active).product_variant_ids._count_quality_points() product_tmpl.quality_control_point_qty = product_tmpl.with_context(
active_test=product_tmpl.active).product_variant_ids._count_quality_points()
def action_see_quality_control_points(self): def action_see_quality_control_points(self):
self.ensure_one() self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id("quality_control.quality_point_action") action = self.env["ir.actions.actions"]._for_xml_id("quality_control.quality_point_action")
action['context'] = dict(self.env.context, default_product_ids=self.product_variant_ids.ids) action['context'] = dict(self.env.context, default_product_ids=self.product_variant_ids.ids)
domain_in_products_or_categs = ['|', ('product_ids', 'in', self.product_variant_ids.ids), ('product_category_ids', 'parent_of', self.categ_id.ids)] domain_in_products_or_categs = ['|', ('product_ids', 'in', self.product_variant_ids.ids),
('product_category_ids', 'parent_of', self.categ_id.ids)]
domain_no_products_and_categs = [('product_ids', '=', False), ('product_category_ids', '=', False)] domain_no_products_and_categs = [('product_ids', '=', False), ('product_category_ids', '=', False)]
action['domain'] = OR([domain_in_products_or_categs, domain_no_products_and_categs]) action['domain'] = OR([domain_in_products_or_categs, domain_no_products_and_categs])
return action return action
@@ -412,10 +903,10 @@ class ProductTemplate(models.Model):
action['context'] = dict(self.env.context, default_product_id=self.product_variant_id.id, create=False) action['context'] = dict(self.env.context, default_product_id=self.product_variant_id.id, create=False)
action['domain'] = [ action['domain'] = [
'|', '|',
('product_id', 'in', self.product_variant_ids.ids), ('product_id', 'in', self.product_variant_ids.ids),
'&', '&',
('measure_on', '=', 'operation'), ('measure_on', '=', 'operation'),
('picking_id.move_ids.product_tmpl_id', '=', self.id), ('picking_id.move_ids.product_tmpl_id', '=', self.id),
] ]
return action return action
@@ -423,7 +914,8 @@ class ProductTemplate(models.Model):
class ProductProduct(models.Model): class ProductProduct(models.Model):
_inherit = "product.product" _inherit = "product.product"
quality_control_point_qty = fields.Integer(compute='_compute_quality_check_qty', groups='quality.group_quality_user') quality_control_point_qty = fields.Integer(compute='_compute_quality_check_qty',
groups='quality.group_quality_user')
quality_pass_qty = fields.Integer(compute='_compute_quality_check_qty', groups='quality.group_quality_user') quality_pass_qty = fields.Integer(compute='_compute_quality_check_qty', groups='quality.group_quality_user')
quality_fail_qty = fields.Integer(compute='_compute_quality_check_qty', groups='quality.group_quality_user') quality_fail_qty = fields.Integer(compute='_compute_quality_check_qty', groups='quality.group_quality_user')
@@ -437,10 +929,10 @@ class ProductProduct(models.Model):
quality_pass_qty = 0 quality_pass_qty = 0
domain = [ domain = [
'|', '|',
('product_id', 'in', self.ids), ('product_id', 'in', self.ids),
'&', '&',
('measure_on', '=', 'operation'), ('measure_on', '=', 'operation'),
('picking_id.move_ids.product_id', 'in', self.ids), ('picking_id.move_ids.product_id', 'in', self.ids),
('company_id', '=', self.env.company.id), ('company_id', '=', self.env.company.id),
('quality_state', '!=', 'none') ('quality_state', '!=', 'none')
] ]
@@ -464,7 +956,8 @@ class ProductProduct(models.Model):
_, where_clause, where_clause_args = query.get_sql() _, where_clause, where_clause_args = query.get_sql()
additional_where_clause = self._additional_quality_point_where_clause() additional_where_clause = self._additional_quality_point_where_clause()
where_clause += additional_where_clause where_clause += additional_where_clause
parent_category_ids = [int(parent_id) for parent_id in self.categ_id.parent_path.split('/')[:-1]] if self.categ_id else [] parent_category_ids = [int(parent_id) for parent_id in
self.categ_id.parent_path.split('/')[:-1]] if self.categ_id else []
self.env.cr.execute(""" self.env.cr.execute("""
SELECT COUNT(*) SELECT COUNT(*)
@@ -485,7 +978,7 @@ class ProductProduct(models.Model):
) )
) )
""" % (where_clause,), where_clause_args + [self.ids, parent_category_ids] """ % (where_clause,), where_clause_args + [self.ids, parent_category_ids]
) )
return self.env.cr.fetchone()[0] return self.env.cr.fetchone()[0]
def action_see_quality_control_points(self): def action_see_quality_control_points(self):
@@ -493,7 +986,8 @@ class ProductProduct(models.Model):
action = self.product_tmpl_id.action_see_quality_control_points() action = self.product_tmpl_id.action_see_quality_control_points()
action['context'].update(default_product_ids=self.ids) action['context'].update(default_product_ids=self.ids)
domain_in_products_or_categs = ['|', ('product_ids', 'in', self.ids), ('product_category_ids', 'parent_of', self.categ_id.ids)] domain_in_products_or_categs = ['|', ('product_ids', 'in', self.ids),
('product_category_ids', 'parent_of', self.categ_id.ids)]
domain_no_products_and_categs = [('product_ids', '=', False), ('product_category_ids', '=', False)] domain_no_products_and_categs = [('product_ids', '=', False), ('product_category_ids', '=', False)]
action['domain'] = OR([domain_in_products_or_categs, domain_no_products_and_categs]) action['domain'] = OR([domain_in_products_or_categs, domain_no_products_and_categs])
return action return action
@@ -504,12 +998,75 @@ class ProductProduct(models.Model):
action['context'] = dict(self.env.context, default_product_id=self.id, create=False) action['context'] = dict(self.env.context, default_product_id=self.id, create=False)
action['domain'] = [ action['domain'] = [
'|', '|',
('product_id', '=', self.id), ('product_id', '=', self.id),
'&', '&',
('measure_on', '=', 'operation'), ('measure_on', '=', 'operation'),
('picking_id.move_ids.product_id', '=', self.id), ('picking_id.move_ids.product_id', '=', self.id),
] ]
return action return action
def _additional_quality_point_where_clause(self): def _additional_quality_point_where_clause(self):
return "" return ""
class QualityCheckMeasureLine(models.Model):
_name = 'quality.check.measure.line'
_description = '质检测量明细'
_order = 'sequence, id'
sequence = fields.Integer('序号')
check_id = fields.Many2one('quality.check', string='质检单', required=True, ondelete='cascade')
# 基本信息
product_name = fields.Char('产品名称', related='check_id.product_id.name', readonly=True)
drawing_no = fields.Char('图号')
measure_item = fields.Char('检测项目')
# 测量值
measure_value1 = fields.Char('测量值1')
measure_value2 = fields.Char('测量值2')
measure_value3 = fields.Char('测量值3')
measure_value4 = fields.Char('测量值4')
measure_value5 = fields.Char('测量值5')
# # 展示列数
# column_nums = fields.Integer('列数', related='check_id.column_nums')
# 判定结果
measure_result = fields.Selection([
('OK', '合格'),
('NG', '不合格')
], string='判定', default='OK')
remark = fields.Char('备注')
def del_measure_value(self):
self.ensure_one()
self.sudo().unlink()
# 增加出厂检验报告发布历史
class QualityCheckReportHistory(models.Model):
_name = 'quality.check.report.history'
_description = '出厂检验报告发布历史'
check_id = fields.Many2one('quality.check', string='质检单', required=True, ondelete='cascade')
report_number_id = fields.Many2one('documents.document', string='报告编号', readonly=True)
sequence = fields.Integer('序号')
# 操作(发布、撤销发布、重新发布)
action = fields.Selection([
('publish', '发布'),
('cancel_publish', '撤销发布'),
('re_publish', '重新发布')
], string='操作')
# 操作人
operator = fields.Char('操作人')
# 操作时间
operation_time = fields.Datetime('操作时间')
# 文档状态(已发布、废弃)
document_status = fields.Selection([
('published', '已发布'),
('canceled', '废弃')
], string='操作后文档状态')

View File

@@ -81,18 +81,38 @@ class StockPicking(models.Model):
return quality_pickings return quality_pickings
def action_cancel(self): def action_cancel(self):
"""
调拨单取消后,关联取消质量检查单
"""
context = self.env.context
if not context.get('cancel_check_picking') and self.sudo().mapped('check_ids').filtered(
lambda x: x.quality_state in ['pass', 'fail']):
self.env.cr.rollback()
return {
'type': 'ir.actions.act_window',
'res_model': 'picking.check.cancel.wizard',
'name': '取消质检单',
'view_mode': 'form',
'target': 'new',
'context': {
'default_picking_id': self.id,
'cancel_check_picking': True}
}
elif self.check_ids.filtered(lambda x: x.quality_state != 'cancel'):
self.sudo().mapped('check_ids').filtered(lambda x: x.quality_state != 'cancel').write({
'quality_state': 'cancel'
})
res = super(StockPicking, self).action_cancel() res = super(StockPicking, self).action_cancel()
self.sudo().mapped('check_ids').filtered(lambda x: x.quality_state == 'none').unlink() # self.sudo().mapped('check_ids').filtered(lambda x: x.quality_state == 'none').unlink()
return res return res
def action_open_quality_check_picking(self): def action_open_quality_check_picking(self):
action = self.env["ir.actions.actions"]._for_xml_id("quality_control.quality_check_action_picking") action = self.env["ir.actions.actions"]._for_xml_id("quality_control.quality_check_action_picking")
action['context'] = self.env.context.copy() action['context'] = {
action['context'].update({
'search_default_picking_id': [self.id], 'search_default_picking_id': [self.id],
'default_picking_id': self.id, 'default_picking_id': self.id,
'show_lots_text': self.show_lots_text, 'show_lots_text': self.show_lots_text,
}) }
return action return action
def button_quality_alert(self): def button_quality_alert(self):

View File

@@ -1,2 +1,7 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_quality_check_wizard,access.quality_check_wizard,model_quality_check_wizard,quality.group_quality_user,1,1,1,0 access_quality_check_wizard,access.quality_check_wizard,model_quality_check_wizard,quality.group_quality_user,1,1,1,0
access_quality_check_measure_line,quality.check.measure.line,model_quality_check_measure_line,base.group_user,1,1,1,0
access_quality_check_import_complex_model_wizard,quality.check.import.complex.model.wizard,model_quality_check_import_complex_model_wizard,quality.group_quality_user,1,1,1,0
access_quality_check_report_history,quality.check.report.history,model_quality_check_report_history,quality.group_quality_user,1,1,1,0
access_quality_check_publish_wizard,quality.check.publish.wizard,model_quality_check_publish_wizard,quality.group_quality_user,1,1,1,0
access_picking_check_cancel_wizard,access.picking_check_cancel_wizard,model_picking_check_cancel_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
2 access_quality_check_wizard access.quality_check_wizard model_quality_check_wizard quality.group_quality_user 1 1 1 0
3 access_quality_check_measure_line quality.check.measure.line model_quality_check_measure_line base.group_user 1 1 1 0
4 access_quality_check_import_complex_model_wizard quality.check.import.complex.model.wizard model_quality_check_import_complex_model_wizard quality.group_quality_user 1 1 1 0
5 access_quality_check_report_history quality.check.report.history model_quality_check_report_history quality.group_quality_user 1 1 1 0
6 access_quality_check_publish_wizard quality.check.publish.wizard model_quality_check_publish_wizard quality.group_quality_user 1 1 1 0
7 access_picking_check_cancel_wizard access.picking_check_cancel_wizard model_picking_check_cancel_wizard quality.group_quality_user 1 1 1 0

View File

@@ -4,3 +4,11 @@
min-height: 250px; min-height: 250px;
} }
} }
.measureTableSequence {
width: 58px;
}
.measureTable .o_list_table_ungrouped {
min-width: auto;
}

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="quality_check_measure_line_tree" model="ir.ui.view">
<field name="name">quality.check.measure.line.tree</field>
<field name="model">quality.check.measure.line</field>
<field name="arch" type="xml">
<tree editable="bottom" class="measureTable">
<!-- <field name="sequence" class="measureTableSequence"/> -->
<!-- <field name="column_nums"/> -->
<field name="measure_item"/>
<field name="measure_value1" attrs="{ 'column_invisible': [('parent.column_nums', '&lt;', 1)] }"/>
<field name="measure_value2" attrs="{ 'column_invisible': [('parent.column_nums', '&lt;', 2)] }"/>
<field name="measure_value3" attrs="{ 'column_invisible': [('parent.column_nums', '&lt;', 3)] }"/>
<field name="measure_value4" attrs="{ 'column_invisible': [('parent.column_nums', '&lt;', 4)] }"/>
<field name="measure_value5" attrs="{ 'column_invisible': [('parent.column_nums', '&lt;', 5)] }"/>
<field name="measure_result"/>
<field name="remark"/>
<button name="del_measure_value" type="object" string="删除" class="btn-danger" attrs="{'invisible': [('parent.publish_status', '=', 'published')]}"/>
</tree>
</field>
</record>
<record id="quality_check_measure_line_form" model="ir.ui.view">
<field name="name">quality.check.measure.line.form</field>
<field name="model">quality.check.measure.line</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group>
<field name="measure_item"/>
<field name="remark"/>
</group>
<group>
<field name="measure_result"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
</odoo>

View File

@@ -255,6 +255,7 @@
<field name="quality_state" widget="statusbar"/> <field name="quality_state" widget="statusbar"/>
</header> </header>
<sheet> <sheet>
<field name="is_out_check" invisible="1"/>
<div class="oe_button_box" name="button_box"> <div class="oe_button_box" name="button_box">
<button name="action_see_alerts" icon="fa-bell" type="object" class="oe_stat_button" <button name="action_see_alerts" icon="fa-bell" type="object" class="oe_stat_button"
attrs="{'invisible': [('alert_count', '=', 0)]}"> attrs="{'invisible': [('alert_count', '=', 0)]}">
@@ -264,10 +265,17 @@
<group> <group>
<group> <group>
<field name="company_id" invisible="1"/> <field name="company_id" invisible="1"/>
<field name="categ_type" invisible="1"/>
<field name="product_id" attrs="{'invisible' : [('measure_on', '=', 'operation')]}"/> <field name="product_id" attrs="{'invisible' : [('measure_on', '=', 'operation')]}"/>
<field name="measure_on" attrs="{'readonly': [('point_id', '!=', False)]}"/> <field name="part_name" attrs="{'invisible': [('categ_type', '!=', '成品')], 'readonly': [('publish_status', '=', 'published')]}"/>
<field name="part_name"/> <field name="part_number" attrs="{'invisible': [('categ_type', '!=', '成品')], 'readonly': [('publish_status', '=', 'published')]}"/>
<field name="part_number"/> <field name="material_name" attrs="{'invisible': [('categ_type', '!=', '成品')]}"/>
<field name="total_qty" attrs="{'invisible': ['|', ('measure_on', '!=', 'product'), ('is_out_check', '=', False)]}"/>
<field name="check_qty" attrs="{'invisible': ['|', ('measure_on', '!=', 'product'), ('is_out_check', '=', False)], 'readonly': [('publish_status', '=', 'published')]}"/>
<!-- <field name="categ_type"/> -->
<field name="report_number_id" attrs="{'invisible': ['|', ('measure_on', '!=', 'product'), ('is_out_check', '=', False)]}"/>
<field name="column_nums" invisible="1"/>
<field name="publish_status" invisible="1"/>
<field name="show_lot_text" invisible="1"/> <field name="show_lot_text" invisible="1"/>
<field name="move_line_id" invisible="1"/> <field name="move_line_id" invisible="1"/>
<field name="product_tracking" invisible="1"/> <field name="product_tracking" invisible="1"/>
@@ -300,23 +308,47 @@
<field name="alert_ids" invisible="1"/> <field name="alert_ids" invisible="1"/>
</group> </group>
<group> <group>
<field name="picking_id"
attrs="{'invisible': [('quality_state', 'in', ('pass', 'fail')), ('picking_id', '=', False)]}"/>
<field name="point_id"/> <field name="point_id"/>
<field name="measure_on" attrs="{'readonly': [('point_id', '!=', False)]}"/>
<field string="Type" name="test_type_id" options="{'no_open': True, 'no_create': True}" <field string="Type" name="test_type_id" options="{'no_open': True, 'no_create': True}"
attrs="{'readonly': [('point_id', '!=', False)]}"/> attrs="{'readonly': [('point_id', '!=', False)]}"/>
<field name="picking_id"
attrs="{'invisible': [('quality_state', 'in', ('pass', 'fail')), ('picking_id', '=', False)]}"/>
<field name="control_date" invisible="1"/> <field name="control_date" invisible="1"/>
<field name="partner_id" string="Partner"
attrs="{'invisible': [('partner_id', '=', False)]}"/>
<field name="team_id"/> <field name="team_id"/>
<field name="company_id" groups="base.group_multi_company"/> <field name="company_id" groups="base.group_multi_company"/>
<field name="user_id" string="Control Person" invisible="1"/> <field name="user_id" string="Control Person" invisible="1"/>
<field name="partner_id" string="Partner" <field name="measure_operator" string="操机员" attrs="{'invisible': ['|', ('measure_on', '!=', 'product'), ('is_out_check', '=', False)], 'readonly': [('publish_status', '=', 'published')]}"/>
attrs="{'invisible': [('partner_id', '=', False)]}"/>
</group> </group>
</group> </group>
<group attrs="{'invisible': [('test_type', '!=', 'picture')]}"> <group attrs="{'invisible': [('test_type', '!=', 'picture')]}">
<field name="picture" widget="image"/> <field name="picture" widget="image"/>
</group> </group>
<notebook> <notebook>
<!-- 增加page页测量、出厂检验报告、2D加工图纸、质检标准、发布历史它们均在is_out_check为True时显示 -->
<!-- 测量page内有一个添加测量值按钮点击可以新增一行测量值新增的行在tree视图中显示显示的列有序号、检测项目、测量值1、测量值2、测量值3、测量值4、测量值5、判定、备注。其中检测项目、测量值1、测量值2、测量值3、测量值4、测量值5为必填项判定为下拉框默认选中合格备注为文本框。 -->
<page string="测量" name="measure" attrs="{'invisible': [('is_out_check', '=', False)]}">
<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"
class="btn-primary"
attrs="{'force_show':1, 'invisible': [('publish_status', '=', 'published')]}"
context="{'default_model_name': 'quality.check.measure.line', 'default_check_id': id}"/>
</div>
<br/>
<div class="o_row">
<field name="measure_line_ids" widget="tree" attrs="{'readonly': [('publish_status', '=', 'published')]}"/>
</div>
</page>
<page string="出厂检验报告" name="out_check" attrs="{'invisible': [('is_out_check', '=', False)]}">
<field name="report_content" widget="adaptive_viewer"/>
</page>
<page string="Notes" name="notes"> <page string="Notes" name="notes">
<group> <group>
<field name="report_result"/> <field name="report_result"/>
@@ -326,6 +358,18 @@
</group> </group>
</page> </page>
<page string="发布历史" name="release_history" attrs="{'invisible': [('is_out_check', '=', False)]}">
<field name="report_history_ids">
<tree>
<field name="sequence"/>
<field name="report_number_id"/>
<field name="action"/>
<field name="document_status"/>
<field name="operation_time"/>
<field name="operator"/>
</tree>
</field>
</page>
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter"> <div class="oe_chatter">

View File

@@ -2,3 +2,5 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import quality_check_wizard from . import quality_check_wizard
from . import import_complex_model
from . import quality_wizard

View File

@@ -0,0 +1,439 @@
# -*- coding: utf-8 -*-
import base64
import logging
import os
import traceback
from datetime import datetime
from io import BytesIO
from openpyxl import load_workbook
import pandas as pd
import xlrd
from odoo import fields, models, api, Command, _
# from odoo.exceptions import ValidationError
from odoo.exceptions import UserError
from datetime import datetime, timedelta
from odoo.http import request
_logger = logging.getLogger(__name__)
class ImportComplexModelWizard(models.TransientModel):
_name = 'quality.check.import.complex.model.wizard'
file_data = fields.Binary("数据文件")
model_name = fields.Char(string='Model Name')
field_basis = fields.Char(string='Field Basis')
check_id = fields.Many2one(string='质检单', comodel_name='quality.check')
def get_model_column_name_labels(self, model):
fields_info = model.fields_get()
# 提取字段名称和展示名称
field_labels = {field_info.get('string'): field_info for field_name, field_info in fields_info.items()}
return field_labels
def field_name_mapping(selfcolumn, column, field_data):
return {}
@api.model
def page_to_field(self, field_data):
return {}
def count_continuous_none(self, df):
# 用于存储间断的连续空值的区间
none_intervals = []
# if pd.isna(val):
# 遍历数据并查找连续的 None
start = 0
num = 0
for index, row in df.iterrows():
if pd.isna(row[0]):
continue
else:
# 记录区间
if num == 0:
start = index
num = 1
else:
end = index
none_intervals.append({'start': start, 'end': index})
start = end
# start = None # 重置
# 检查最后一个区间
if len(df) - start >= 1:
none_intervals.append({'start': start, 'end': len(df)})
return none_intervals
def find_repeated_ranges(self, data):
# 判断内联列表范围有column列的为内联列表字段
if not data:
return []
repeats = []
start_index = 0
current_value = data[0]
for i in range(1, len(data)):
if data[i] == current_value:
continue
else:
if i - start_index > 1: # 有重复
repeats.append({'start': start_index, 'end': i, 'column': data[i - 1]})
# repeats.append((current_value, start_index, i - 1))
current_value = data[i]
start_index = i
# 检查最后一段
if len(data) - start_index > 1:
repeats.append({'start': start_index, 'end': i, 'column': data[i - 1]})
# repeats.append((current_value, start_index, len(data) - 1))
return repeats
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()
# 解码文件数据
file_content = base64.b64decode(self.file_data)
try:
# 使用xlrd读取Excel文件
workbook = xlrd.open_workbook(file_contents=file_content)
sheet = workbook.sheet_by_index(0)
# 检查表头是否符合预期(忽略星号)
expected_headers = ['产品名称', '图号', '检测项目', '测量值1', '测量值2', '测量值3', '测量值4', '测量值5', '判定', '备注']
actual_headers = sheet.row_values(0)
# 处理合并单元格的情况
# 获取所有合并单元格
merged_cells = []
for crange in sheet.merged_cells:
rlo, rhi, clo, chi = crange
if rlo == 0: # 只关注第一行(表头)的合并单元格
merged_cells.append((clo, chi))
# 清理表头(移除星号和处理空值)
actual_headers_clean = []
for i, header in enumerate(actual_headers):
# 检查当前列是否在合并单元格范围内但不是合并单元格的起始列
is_merged_not_first = any(clo < i < chi for clo, chi in merged_cells)
if is_merged_not_first:
# 如果是合并单元格的非起始列,跳过
continue
# 处理表头文本
if isinstance(header, str):
header = header.replace('*', '').strip()
if header: # 只添加非空表头
actual_headers_clean.append(header)
# 检查表头数量
if len(actual_headers_clean) < len(expected_headers):
raise UserError(_('表头列数不足,请使用正确的模板文件'))
# 检查表头顺序(忽略星号)
for i, header in enumerate(expected_headers):
if i >= len(actual_headers_clean) or header != actual_headers_clean[i]:
actual_header = actual_headers_clean[i] if i < len(actual_headers_clean) else "缺失"
raise UserError(_('表头顺序不正确,第%s列应为"%s",但实际为"%s"') %
(i + 1, header, actual_header))
# 获取当前活动的quality.check记录
active_id = self.env.context.get('active_id')
quality_check = self.env['quality.check'].browse(active_id)
if not quality_check:
raise UserError(_('未找到相关的质检记录'))
# 记录是否有有效数据被导入
valid_data_imported = False
# 从第二行开始读取数据(跳过表头)
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
# 创建quality.check.measure.line记录
measure_line_vals = {
'check_id': quality_check.id,
'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_result': 'NG' if row[9] == 'NG' else 'OK', # 判定列
'remark': row[10] if len(row) > 10 and row[10] else '', # 备注列
}
for i in range(1, 6):
if measure_line_vals.get(f'measure_value{i}'):
if i > max_columns:
max_columns = i
self.env['quality.check.measure.line'].create(measure_line_vals)
valid_data_imported = True
quality_check.column_nums = max_columns
# 检查是否有有效数据被导入
if not valid_data_imported:
raise UserError(_('表格中没有有效数据行可导入,请检查表格内容'))
# 返回关闭弹窗的动作
return {
'type': 'ir.actions.act_window_close'
}
except Exception as e:
_logger.error("导入Excel数据时出错: %s", str(e))
_logger.error(traceback.format_exc())
raise UserError(_('导入失败: %s') % str(e))
def process_first_line(self, df_columns, column_labels, field_data):
columns = []
last_column_name = None
for index, column in enumerate(df_columns):
if not column_labels.get(column):
if 'Unnamed' in column:
columns.append(last_column_name)
else:
field_name_map = self.page_to_field(field_data)
field_name = field_name_map.get(column)
if field_name:
columns.append(field_name)
last_column_name = field_name
else:
columns.append(column)
last_column_name = column
else:
columns.append(column)
last_column_name = column
return columns
def process_inline_list_column(self, columns, first_row, repeat_list):
for index, repeat_map in enumerate(repeat_list):
start = repeat_map.get('start')
end = int(repeat_map.get('end'))
if len(repeat_list) - 1 == index:
end = end + 1
for i in range(start, end):
field_name = columns[i]
embedded_fields = first_row[i]
if pd.isna(embedded_fields):
columns[i] = field_name
else:
columns[i] = field_name + '?' + embedded_fields
def process_data_list(self, model, data_list, column_labels, sheet):
try:
for index, data in enumerate(data_list):
# 转换每行数据到模型data = {dict: 11} {'刀具物料': '刀片', '刀尖特征': '刀尖倒角', '刀片形状': '五边形', '刀片物料参数': [{'刀片物料参数?内接圆直径IC/D(mm)': 23, '刀片物料参数?刀尖圆弧半径RE(mm)': 2, '刀片物料参数?刀片牙型': '石油管螺纹刀片', '刀片物料参数?切削刃长(mm)': 3, '刀片物料参数?厚度(mm)': 12, '刀片物料参数?后角(mm)': 4, '刀片物料参数?安装孔直径D1(mm)': 2, '刀片物料参数?有无断屑槽': '无', '刀片物料参数?物…视图
self.import_model_record(model, data, column_labels, sheet)
except Exception as e:
traceback_error = traceback.format_exc()
logging.error('批量导入失败sheet %s' % sheet)
logging.error('批量导入失败 : %s' % traceback_error)
raise UserError(e)
@api.model
def process_model_record_data(self, model, data, column_labels, sheet):
pass
def import_model_record(self, model, data, column_labels, sheet):
self.process_model_record_data(model, data, column_labels, sheet)
model_data = self.convert_column_name(data, column_labels)
logging.info('批量导入模型{} 数据: {}'.format(model, model_data))
new_model = model.create(model_data)
self.model_subsequent_processing(new_model, data)
@api.model
def model_subsequent_processing(self, model, data):
pass
def convert_column_name(self, data, column_labels):
tmp_map = {}
for key, value in data.items():
if not column_labels.get(key):
continue
if key == "执行标准":
print('fqwioiqwfo ', value, column_labels)
field_info = column_labels.get(key)
tmp_map[field_info.get('name')] = self.process_field_data(value, field_info)
return tmp_map
def process_field_data(self, value, field_info):
relation_field_types = ['many2one', 'one2many', 'many2many']
field_type = field_info.get('type')
if field_type not in relation_field_types:
return value
if field_type == 'Boolean':
if value == '' or value == '':
return True
else:
return False
if field_type == 'many2one':
relation_info = self.env[field_info.get('relation')].sudo().search([('name', '=', value)], limit=1)
if relation_info:
return int(relation_info)
if isinstance(value, list):
return self.process_basic_data_list(value, field_info)
else:
relation_info = self.env[field_info.get('relation')].sudo().search([('name', '=', value)], limit=1)
if relation_info:
return [Command.link(int(relation_info))]
def process_basic_data_list(self, value, field_info):
if self.is_basic_data_list(value):
return [
Command.link(
int(self.env[field_info.get('relation')].sudo().search([('name', '=', element)], limit=1)))
for element in value
]
else:
association_column_labels = self.get_model_column_name_labels(
self.env[field_info.get('relation')].sudo())
data_list = [
{column_name.split('?')[1]: column_value
for column_name, column_value in association_column_data.items()
if
len(column_name.split('?')) == 2 and association_column_labels.get(column_name.split('?')[1])}
for association_column_data in value
]
data_list = [self.convert_column_name(element, association_column_labels) for element in data_list]
return [
Command.create(
column_map
) for column_map in data_list
]
def get_remaining_ranges(self, ranges, full_list_length):
# 创建一个集合用于存储被覆盖的索引
covered_indices = set()
# 遍历范围集合,标记覆盖的索引
for r in ranges:
start = r['start']
end = r['end']
# 将该范围内的索引加入集合
covered_indices.update(range(start, end + 1))
# 计算未覆盖的范围
remaining_ranges = []
start = None
for i in range(full_list_length):
if i not in covered_indices:
if start is None:
start = i # 开始新的未覆盖范围
else:
if start is not None:
# 记录当前未覆盖范围
remaining_ranges.append({'start': start, 'end': i - 1})
start = None # 重置
# 处理最后一个范围
if start is not None:
remaining_ranges.append({'start': start, 'end': full_list_length - 1})
return remaining_ranges
def is_basic_data_list(self, lst):
basic_types = (int, float, str, bool)
lst_len = len(lst)
if lst_len < 1:
return False
if isinstance(lst[0], basic_types):
return True
return False
def index_retrieve_data(self, df, range_map, i, data_map):
for remaining_map in range_map:
relation_column_data_map = {}
for j in range(remaining_map.get('start'), int(remaining_map.get('end')) + 1):
value = df.iat[i, j]
if pd.isna(value):
continue
if remaining_map.get('column'):
relation_column_data_map.update({df.columns[j]: value})
else:
if not data_map.get(df.columns[j]):
data_map.update({df.columns[j]: value})
elif isinstance(data_map.get(df.columns[j]), list):
data_map.get(df.columns[j]).append(value)
else:
lst = [data_map.get(df.columns[j]), value]
data_map.update({df.columns[j]: lst})
if relation_column_data_map and len(relation_column_data_map) > 0:
data_map.setdefault(remaining_map.get('column'), []).append(relation_column_data_map)
def parse_excel_data_matrix(self, df, repeat_list):
row_interval_list = self.count_continuous_none(df)
remaining_ranges = self.get_remaining_ranges(repeat_list, len(df.columns))
data_list = []
for row_interval_map in row_interval_list:
data_map = {}
for index in range(row_interval_map.get('start'), int(row_interval_map.get('end'))):
if index == 0:
self.index_retrieve_data(df, remaining_ranges, index, data_map)
else:
self.index_retrieve_data(df, remaining_ranges, index, data_map)
self.index_retrieve_data(df, repeat_list, index, data_map)
if len(data_map) > 0:
data_list.append(data_map)
return data_list
def saadqw(self):
excel_template = self.env['excel.template'].sudo().search([('model_id.model', '=', self.model_name)], limit=1)
file_content = base64.b64decode(excel_template.file_data)
return {
'type': 'ir.actions.act_url',
'url': 'data:application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;base64,{file_content}',
'target': 'self',
'download': excel_template.file_name,
}
# return request.make_response(
# file_content,
# headers=[
# ('Content-Disposition', f'attachment; filename="{excel_template.file_name}"'),
# ('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'),
# ]
# )
def download_excel_template(self):
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://"):
excel_url = base_url.replace("http://", "https://")
else:
excel_url = base_url
value = dict(
type='ir.actions.act_url',
target='self',
url=excel_url,
)
return value

View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="quality_check_import_complex_model_wizard_form" model="ir.ui.view">
<field name="name">请导入数据文件</field>
<field name="model">quality.check.import.complex.model.wizard</field>
<field name="arch" type="xml">
<form>
<group>
<field name="file_data" widget="binary" options="{'accepted_file_extensions': '.xlsx'}"/>
</group>
<footer>
<button string="确认导入" name="import_data" type="object" class="btn-primary"/>
<button string="取消" class="btn-primary" special="cancel"/>
<button string="模板文件下载" name="download_excel_template" type="object" class="btn-primary"/>
</footer>
</form>
</field>
</record>
<record id="import_complex_model_wizard" model="ir.actions.act_window">
<field name="name">导入模型数据</field>
<field name="type">ir.actions.act_window</field>
<!-- <field name="res_model">up.select.wizard</field>-->
<field name="res_model">quality.check.import.complex.model.wizard</field>
<field name="view_mode">form</field>
<field name="view_id" ref="quality_check_import_complex_model_wizard_form"/>
<field name="target">new</field>
<!-- <field name="context"></field>-->
</record>
</odoo>

View File

@@ -62,7 +62,8 @@ class QualityCheckWizard(models.TransientModel):
def do_pass(self): def do_pass(self):
if self.test_type == 'picture' and not self.picture: if self.test_type == 'picture' and not self.picture:
raise UserError('You must provide a picture before validating') raise UserError('请先上传照片')
# raise UserError('You must provide a picture before validating')
self.current_check_id.do_pass() self.current_check_id.do_pass()
return self.action_generate_next_window() return self.action_generate_next_window()
@@ -112,3 +113,25 @@ class QualityCheckWizard(models.TransientModel):
default_current_check_id=self.current_check_id.id, default_current_check_id=self.current_check_id.id,
) )
return action return action
# 对于成品出库的出厂检验报告,增加发布按钮
def publish(self):
self.current_check_id._check_part_number()
self.current_check_id._check_measure_line()
self.current_check_id._check_check_qty_and_total_qty()
self.current_check_id._do_publish_implementation()
class PickingCheckCancelWizard(models.TransientModel):
_name = 'picking.check.cancel.wizard'
_description = 'picking check cancel wizard'
picking_id = fields.Many2one('stock.picking', 'stock picking')
def confirm_picking_check(self):
res = self.picking_id.action_cancel()
return res
def cancel_picking_check(self):
# 这里是取消后的逻辑
return {'type': 'ir.actions.act_window_close'}

View File

@@ -79,7 +79,10 @@
attrs="{'invisible': ['|', ('quality_state', '!=', 'none'), ('test_type', '!=', 'passfail')]}" data-hotkey="x"/> attrs="{'invisible': ['|', ('quality_state', '!=', 'none'), ('test_type', '!=', 'passfail')]}" data-hotkey="x"/>
<button name="action_generate_previous_window" type="object" class="btn-secondary" string="Previous" attrs="{'invisible': [('position_current_check', '=', 1)]}"/> <button name="action_generate_previous_window" type="object" class="btn-secondary" string="Previous" attrs="{'invisible': [('position_current_check', '=', 1)]}"/>
<button name="action_generate_next_window" type="object" class="btn-secondary" string="Next" attrs="{'invisible': [('is_last_check', '=', True)]}"/> <button name="action_generate_next_window" type="object" class="btn-secondary" string="Next" attrs="{'invisible': [('is_last_check', '=', True)]}"/>
<button string="发布" name="publish" type="object" class="btn-primary"
attrs="{'invisible': ['|', ('quality_state', '!=', 'none'), ('test_type', '!=', 'factory_inspection')]}" data-hotkey="p"/>
<button string="Cancel" class="btn btn-secondary" special="cancel" data-hotkey="z" /> <button string="Cancel" class="btn btn-secondary" special="cancel" data-hotkey="z" />
</footer> </footer>
</form> </form>
</field> </field>
@@ -118,4 +121,21 @@
<field name="context">{}</field> <field name="context">{}</field>
<field name="target">new</field> <field name="target">new</field>
</record> </record>
<!-- ================================================================================================== -->
<record id="picking_check_cancel_wizard_form" model="ir.ui.view">
<field name="name">picking.check.cancel.wizard</field>
<field name="model">picking.check.cancel.wizard</field>
<field name="arch" type="xml">
<form string="Quality Check Failed">
<div>质量检查单已完成,继续取消吗?</div>
<div class="'color': 'red'">注意:关联质量检查单也将被取消。</div>
<footer>
<button name="confirm_picking_check" type="object" class="btn-primary" string="确认"/>
<button name="cancel_picking_check" type="object" string="取消"/>
</footer>
</form>
</field>
</record>
</odoo> </odoo>

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from odoo import api, fields, models, _
class QualityCheckPublishWizard(models.TransientModel):
_name = 'quality.check.publish.wizard'
_description = '质检报告发布确认'
check_id = fields.Many2one('quality.check', string='质检单', required=True)
product_name = fields.Char('产品名称', readonly=True)
total_qty = fields.Char('总数量', readonly=True)
check_qty = fields.Char('检验数', readonly=True)
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)
def action_confirm_publish(self):
"""确认发布"""
self.ensure_one()
return self.check_id._do_publish_implementation()

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_quality_check_publish_wizard_form" model="ir.ui.view">
<field name="name">quality.check.publish.wizard.form</field>
<field name="model">quality.check.publish.wizard</field>
<field name="arch" type="xml">
<form string="发布确认">
<div class="alert alert-info" role="alert" style="margin-bottom:0px;">
<p>您即将发布出厂检验报告:产品<strong><field name="product_name" class="oe_inline"/></strong>,总数量<strong><field name="total_qty" class="oe_inline"/></strong>,检验数<strong><field name="check_qty" class="oe_inline"/></strong>,测量<strong><field name="measure_count" class="oe_inline"/></strong>件,检验项目<strong><field name="item_count" class="oe_inline"/></strong>项。</p>
<field name="publish_status" invisible="1"/>
<!-- 状态为draft时显示 -->
<div attrs="{'invisible': [('publish_status', '!=', 'draft')]}">
<span style="font-weight:bold;">
注意:发布后所有用户可扫描下载本报告
</span>
</div>
<!-- 状态不为draft时显示 -->
<div attrs="{'invisible': [('publish_status', '=', 'draft')]}">
<span style="font-weight:bold;">
注意:已发布的报告
<field name="old_report_name" readonly="1"
style="color:red;"
attrs="{'invisible': [('old_report_name', '=', False)]}"/>
<span style="color:red;"
attrs="{'invisible': [('old_report_name', '!=', False)]}">(未知报告编号)</span>
可能已被客户下载
</span>
</div>
</div>
<footer>
<button name="action_confirm_publish" string="发布" type="object" class="btn-primary"/>
<button string="取消" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@@ -65,7 +65,7 @@
<field name="lot_id" position="after"> <field name="lot_id" position="after">
<field name="workorder_id" invisible="1"/> <field name="workorder_id" invisible="1"/>
<field name="production_id" invisible="1"/> <field name="production_id" invisible="1"/>
<field name="finished_lot_id" attrs="{'invisible': [('finished_lot_id', '=', False)]}" groups="stock.group_production_lot"/> <!-- <field name="finished_lot_id" attrs="{'invisible': [('finished_lot_id', '=', False)]}" groups="stock.group_production_lot"/> -->
</field> </field>
<xpath expr="//field[@name='lot_id']" position="after"> <xpath expr="//field[@name='lot_id']" position="after">
<field name="lot_id" attrs="{'invisible': [('workorder_id', '=', False)]}" groups="stock.group_production_lot" string="Component Lot/Serial"/> <field name="lot_id" attrs="{'invisible': [('workorder_id', '=', False)]}" groups="stock.group_production_lot" string="Component Lot/Serial"/>

View File

@@ -421,3 +421,4 @@ class EmbryoRedundancy(models.Model):
width = fields.Float('宽度(mm)', required=True) width = fields.Float('宽度(mm)', required=True)
height = fields.Float('高度(mm)', required=True) height = fields.Float('高度(mm)', required=True)
active = fields.Boolean('有效', default=True) active = fields.Boolean('有效', default=True)
remark = fields.Char('描述')

View File

@@ -57,15 +57,42 @@ class MrsMaterialModel(models.Model):
remark = fields.Text("备注") remark = fields.Text("备注")
gain_way = fields.Selection( gain_way = fields.Selection(
[("自加工", "自加工"), ("外协", "委外加工"), ("采购", "采购")], [("自加工", "自加工"), ("外协", "委外加工"), ("采购", "采购")],
default="", string="获取方式") default="采购", string="获取方式")
supplier_ids = fields.One2many('sf.supplier.sort', 'materials_model_id', string='供应商') supplier_ids = fields.One2many('sf.supplier.sort', 'materials_model_id', string='供应商')
active = fields.Boolean('有效', default=True) active = fields.Boolean('有效', default=True)
@api.constrains("gain_way") def write(self, vals):
def _check_supplier_ids(self): res = super(MrsMaterialModel, self).write(vals)
for item in self: if not self.gain_way:
if item.gain_way in ('外协', '采购') and not item.supplier_ids: self.gain_way = '采购'
raise UserError("请添加供应商") if not self.supplier_ids:
supplier_id = self.env['res.partner'].search([('name', 'like', '%傲派%')], limit=1)
if not supplier_id:
supplier_id = self.env['res.partner'].create({
'name': '湖南傲派自动化设备有限公司',
'supplier_rank':1,
})
self.supplier_ids = [(0, 0, {'materials_model_id': self.id, 'partner_id': supplier_id.id or False})]
return res
@api.model
def create(self, vals):
res = super(MrsMaterialModel, self).create(vals)
if not vals.get('supplier_ids'):
supplier_id = self.env['res.partner'].search([('name', 'like', '%傲派%')], limit=1)
if not supplier_id:
supplier_id = self.env['res.partner'].create({
'name': '湖南傲派自动化设备有限公司',
'supplier_rank': 1,
})
res.supplier_ids = [(0, 0, {'materials_model_id': res.id, 'partner_id': supplier_id.id or False})]
return res
else:
return res
# @api.constrains("gain_way")
# def _check_supplier_ids(self):
# for item in self:
# if item.gain_way in ('外协', '采购') and not item.supplier_ids:
# raise UserError("请添加供应商")
class MrsProductionProcessCategory(models.Model): class MrsProductionProcessCategory(models.Model):

View File

@@ -32,6 +32,7 @@ class FixtureModel(models.Model):
multi_mounting_type_id = fields.Many2one('sf.multi_mounting.type', string="联装类型") multi_mounting_type_id = fields.Many2one('sf.multi_mounting.type', string="联装类型")
brand_id = fields.Many2one('sf.machine.brand', string="品牌") brand_id = fields.Many2one('sf.machine.brand', string="品牌")
model_file = fields.Binary(string="图片") model_file = fields.Binary(string="图片")
glb_url = fields.Char(string="图片")
status = fields.Boolean('状态') status = fields.Boolean('状态')
active = fields.Boolean('有效', default=False) active = fields.Boolean('有效', default=False)

View File

@@ -645,6 +645,7 @@
<field name="long"/> <field name="long"/>
<field name="width"/> <field name="width"/>
<field name="height"/> <field name="height"/>
<field name="remark"/>
</tree> </tree>
</field> </field>
</record> </record>

View File

@@ -262,13 +262,13 @@
<group> <group>
<field name="materials_no" readonly="1" force_save="1"/> <field name="materials_no" readonly="1" force_save="1"/>
<field name="gain_way" required="0"/> <field name="gain_way" required="0"/>
<field name="tensile_strength" required="1"/> <field name="density" readonly="1" required="1" class="custom_required"/>
<field name="hardness" required="1"/>
<field name="density" readonly="1"/>
</group> </group>
<group> <group>
<field name="rough_machining" required="1"/> <field name="rough_machining" required="1"/>
<field name="finish_machining" required="1"/> <field name="finish_machining" required="1"/>
<field name="tensile_strength" required="1"/>
<field name="hardness" required="1"/>
<field name="need_h" default="false" readonly="1"/> <field name="need_h" default="false" readonly="1"/>
<field name="mf_materia_post" attrs="{'invisible':[('need_h','=',False)]} " <field name="mf_materia_post" attrs="{'invisible':[('need_h','=',False)]} "
readonly="1"/> readonly="1"/>
@@ -297,7 +297,7 @@
<record model="ir.ui.view" id="sf_materials_model_tree"> <record model="ir.ui.view" id="sf_materials_model_tree">
<field name="model">sf.materials.model</field> <field name="model">sf.materials.model</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="材料型号" delete="0"> <tree string="材料型号" delete="0" create="0">
<field name="materials_no"/> <field name="materials_no"/>
<field name="materials_code"/> <field name="materials_code"/>
<field name="name"/> <field name="name"/>

View File

@@ -158,6 +158,8 @@
<!-- <field name="upload_model_file" widget="many2many_binary"/>--> <!-- <field name="upload_model_file" widget="many2many_binary"/>-->
<field name="model_file" widget="Viewer3D" string="模型" readonly="1" force_save="1" <field name="model_file" widget="Viewer3D" string="模型" readonly="1" force_save="1"
attrs="{'invisible': [('model_file', '=', False)]}"/> attrs="{'invisible': [('model_file', '=', False)]}"/>
<field name="glb_url" widget="Viewer3D" string="模型" readonly="1" force_save="1"
attrs="{'invisible': [('glb_url', '=', False)]}"/>
</group> </group>
</group> </group>
<notebook> <notebook>

View File

@@ -29,7 +29,7 @@ class Sf_Bf_Connect(http.Controller):
bfm_process_order_list = json.loads(kw['bfm_process_order_list']) bfm_process_order_list = json.loads(kw['bfm_process_order_list'])
order_id = request.env['sale.order'].with_user(request.env.ref("base.user_admin")).sale_order_create( order_id = request.env['sale.order'].with_user(request.env.ref("base.user_admin")).sale_order_create(
company_id, kw['delivery_name'], kw['delivery_telephone'], kw['delivery_address'], company_id, kw['delivery_name'], kw['delivery_telephone'], kw['delivery_address'],
kw['delivery_end_date'], kw['payments_way'], kw['pay_way']) kw['delivery_end_date'], kw['payments_way'], kw['pay_way'], model_display_version=kw.get('model_display_version'))
i = 1 i = 1
# 给sale_order的default_code字段赋值 # 给sale_order的default_code字段赋值
aa = request.env['sale.order'].sudo().search([('name', '=', order_id.name)]) aa = request.env['sale.order'].sudo().search([('name', '=', order_id.name)])

View File

@@ -32,7 +32,7 @@
<field name="is_bill" invisible="True"/> <field name="is_bill" invisible="True"/>
<field name="logistics_status" invisible="True"/> <field name="logistics_status" invisible="True"/>
<field name="logistics_way" invisible="True"/> <field name="logistics_way" invisible="True"/>
<button string="物流下单" name="create_order" type="object" confirm="是否确认物流下单" class="btn-primary" <!-- <button string="物流下单" name="create_order" type="object" confirm="是否确认物流下单" class="btn-primary" -->
attrs="{'invisible': ['|', '|', '|', ('check_out', '!=', 'OUT'), ('state', '!=', 'assigned'), ('is_bill', '=', True), ('logistics_way', '=', '自提')]}"/> attrs="{'invisible': ['|', '|', '|', ('check_out', '!=', 'OUT'), ('state', '!=', 'assigned'), ('is_bill', '=', True), ('logistics_way', '=', '自提')]}"/>
<button string="获取物流面单" name="get_bill" type="object" confirm="是否获取物流面单" class="btn-primary" <button string="获取物流面单" name="get_bill" type="object" confirm="是否获取物流面单" class="btn-primary"
attrs="{'invisible': ['|', '|', '|', '|', ('check_out', '!=', 'OUT'), ('state', '!=', 'assigned'), ('logistics_status', '=', '2'), ('is_bill', '=', False), ('logistics_way', '=', '自提')]}"/> attrs="{'invisible': ['|', '|', '|', '|', ('check_out', '!=', 'OUT'), ('state', '!=', 'assigned'), ('logistics_status', '=', '2'), ('is_bill', '=', False), ('logistics_way', '=', '自提')]}"/>

View File

@@ -44,7 +44,7 @@ class ResProductTemplate(models.Model):
else: else:
return self.env.ref('sf_dlm.product_uom_cubic_millimeter') return self.env.ref('sf_dlm.product_uom_cubic_millimeter')
# model_file = fields.Binary('模型文件') model_file = fields.Binary('模型文件')
# 胚料的库存路线设置 # 胚料的库存路线设置
# def _get_routes(self, route_type): # def _get_routes(self, route_type):

View File

@@ -6,7 +6,7 @@
<field name="inherit_id" ref="sf_manufacturing.view_mrp_production_workorder_tray_form_inherit_sf"/> <field name="inherit_id" ref="sf_manufacturing.view_mrp_production_workorder_tray_form_inherit_sf"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//page[1]" position="before"> <xpath expr="//page[1]" position="before">
<page string="开料要求" attrs='{"invisible": [("routing_type","!=","切割")]}'> <page string="开料要求" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "CMR")]}'>
<group> <group>
<group> <group>
<field name="product_tmpl_id_materials_id" widget="many2one"/> <field name="product_tmpl_id_materials_id" widget="many2one"/>

View File

@@ -16,15 +16,21 @@
<field name='categ_id' class="custom_required" attrs="{'readonly': [('id', '!=', False)]}"/> <field name='categ_id' class="custom_required" attrs="{'readonly': [('id', '!=', False)]}"/>
<field name='is_bfm' invisible="1"/> <field name='is_bfm' invisible="1"/>
<field name='categ_type' invisible="1"/> <field name='categ_type' invisible="1"/>
<field name='glb_url' invisible="1"/>
<field name='part_name' attrs="{'invisible': [('categ_type', '!=', '成品')]}"/> <field name='part_name' attrs="{'invisible': [('categ_type', '!=', '成品')]}"/>
<field name='part_number' attrs="{'invisible': [('categ_type', '!=', '成品')]}"/> <field name='part_number' attrs="{'invisible': [('categ_type', '!=', '成品')]}"/>
<field name='manual_quotation' attrs="{'invisible':[('upload_model_file', '=', [])]}"/> <field name='manual_quotation' attrs="{'invisible':[('glb_url', '=', False)]}"/>
<field name="is_customer_provided" attrs="{'invisible': [('categ_type', 'not in', ['成品', '坯料'])], 'readonly': True}" /> <field name="is_customer_provided" attrs="{'invisible': [('categ_type', 'not in', ['成品', '坯料'])], 'readonly': True}" />
<field name="upload_model_file" <field name="model_name" invisible="1"/>
widget="many2many_binary" <field name="upload_model_file" widget="many2many_binary" attrs="{'invisible': [('upload_model_file', '=', False)]}"/>
attrs="{'invisible': ['|', '|',('categ_type', '!=', '成品'),('categ_type', '=', False),('is_bfm','=', True)]}"/> <field name="model_url"
widget="binary_download"
filename_field="model_name"
attrs="{'invisible': ['|', '|',('categ_type', '!=', '成品'),('categ_type', '=', False),('model_url', '=', False)]}"/>
<field name="model_file" widget="Viewer3D" string="模型" readonly="1" force_save="1" <field name="model_file" widget="Viewer3D" string="模型" readonly="1" force_save="1"
attrs="{'invisible': ['|','|', ('categ_type', '!=', '成品'),('categ_type', '=', False),('model_file', '=', False)]}"/> attrs="{'invisible': ['|','|', ('categ_type', '!=', '成品'),('categ_type', '=', False),('model_file', '=', False)]}"/>
<field name="glb_url" widget="Viewer3D" string="模型" readonly="1" force_save="1"
attrs="{'invisible': ['|','|', ('categ_type', '!=', '成品'),('categ_type', '=', False),('glb_url', '=', False)]}"/>
<field name='cutting_tool_type' invisible="1"/> <field name='cutting_tool_type' invisible="1"/>
<field name="fixture_material_type" invisible="1"/> <field name="fixture_material_type" invisible="1"/>
<field name="embryo_model_type_id" string="模型类型" options="{'no_create': True}" <field name="embryo_model_type_id" string="模型类型" options="{'no_create': True}"
@@ -68,6 +74,7 @@
</field> </field>
<xpath expr="//field[@name='uom_id']" position="before"> <xpath expr="//field[@name='uom_id']" position="before">
<field name="is_manual_processing" attrs="{'invisible': [('categ_type', 'not in', ['成品', '坯料'])], 'readonly': True}" /> <field name="is_manual_processing" attrs="{'invisible': [('categ_type', 'not in', ['成品', '坯料'])], 'readonly': True}" />
<field name="auto_machining" attrs="{'invisible': [('categ_type', 'not in', ['成品', '坯料'])], 'readonly': True}" />
</xpath> </xpath>
<xpath expr="//label[@for='volume']" position="before"> <xpath expr="//label[@for='volume']" position="before">
<label for="length" string="尺寸" <label for="length" string="尺寸"

View File

@@ -5,7 +5,7 @@ import json
import base64 import base64
import logging import logging
import psycopg2 import psycopg2
from datetime import datetime, timedelta from datetime import datetime, timedelta, timezone
from odoo import http, fields from odoo import http, fields
from odoo.http import request from odoo.http import request
@@ -414,7 +414,7 @@ class Sf_Dashboard_Connect(http.Controller):
# 工单计划量切换为CNC工单 # 工单计划量切换为CNC工单
plan_data_total_counts = work_order_obj.search_count( 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加工')]) ('state', 'in', ['ready', 'progress', 'done']), ('routing_type', '=', 'CNC加工')])
# # 工单完成量 # # 工单完成量
@@ -423,13 +423,13 @@ class Sf_Dashboard_Connect(http.Controller):
# 工单完成量切换为CNC工单 # 工单完成量切换为CNC工单
plan_data_finish_counts = work_order_obj.search_count( 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加工')]) ('state', 'in', ['done']), ('routing_type', '=', 'CNC加工')])
# 超期完成量 # 超期完成量
# 搜索所有已经完成的工单 # 搜索所有已经完成的工单
plan_data_overtime = work_order_obj.search([ plan_data_overtime = work_order_obj.search([
('production_id.production_line_id.name', '=', line), ('production_line_id.name', '=', line),
('state', 'in', ['done']), ('state', 'in', ['done']),
('routing_type', '=', 'CNC加工') ('routing_type', '=', 'CNC加工')
]) ])
@@ -448,9 +448,14 @@ class Sf_Dashboard_Connect(http.Controller):
]) ])
# 过滤出那些检测结果状态为 '返工' 或 '报废' 的记录 # 过滤出那些检测结果状态为 '返工' 或 '报废' 的记录
faulty_plans = plan_data.filtered(lambda p: any( # faulty_plans = plan_data.filtered(lambda p: any(
result.test_results in ['返工', '报废'] for result in p.production_id.detection_result_ids # 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( cancel_order_count = production_obj.search_count(
@@ -566,7 +571,8 @@ class Sf_Dashboard_Connect(http.Controller):
:return: :return:
""" """
res = {'status': 1, 'message': '成功', 'data': {}} res = {'status': 1, 'message': '成功', 'data': {}}
plan_obj = request.env['sf.production.plan'].sudo() # plan_obj = request.env['sf.production.plan'].sudo()
# plan_obj = request.env['mrp.workorder'].sudo().search([('routing_type', '=', 'CNC加工')])
line_list = ast.literal_eval(kw['line_list']) line_list = ast.literal_eval(kw['line_list'])
begin_time_str = kw['begin_time'].strip('"') begin_time_str = kw['begin_time'].strip('"')
end_time_str = kw['end_time'].strip('"') end_time_str = kw['end_time'].strip('"')
@@ -604,7 +610,7 @@ class Sf_Dashboard_Connect(http.Controller):
return date_list return date_list
for line in line_list: for line in line_list:
date_field_name = 'actual_end_time' # 替换为你模型中的实际字段名 date_field_name = 'date_finished' # 替换为你模型中的实际字段名
order_counts = [] order_counts = []
if time_unit == 'hour': if time_unit == 'hour':
@@ -616,11 +622,19 @@ class Sf_Dashboard_Connect(http.Controller):
for time_interval in time_intervals: for time_interval in time_intervals:
start_time, end_time = time_interval start_time, end_time = time_interval
orders = plan_obj.search([ # 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), ('production_line_id.name', '=', line),
('state', 'in', ['finished']), ('state', 'in', ['done']),
(date_field_name, '>=', start_time.strftime('%Y-%m-%d %H:%M:%S')), (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'))
]) ])
# 使用小时和分钟作为键,确保每个小时的数据有独立的键 # 使用小时和分钟作为键,确保每个小时的数据有独立的键
@@ -637,18 +651,22 @@ class Sf_Dashboard_Connect(http.Controller):
for date in date_list: for date in date_list:
next_day = date + timedelta(days=1) next_day = date + timedelta(days=1)
orders = plan_obj.search([('production_line_id.name', '=', line), ('state', 'in', ['finished']), orders = request.env['mrp.workorder'].sudo().search(
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')), [('production_id.production_line_id.name', '=', line), ('state', 'in', ['done']),
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00')) ('routing_type', '=', 'CNC加工'),
])
rework_orders = plan_obj.search(
[('production_line_id.name', '=', line), ('production_id.state', 'in', ['rework']),
(date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')), (date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')),
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00')) (date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
]) ])
not_passed_orders = plan_obj.search(
[('production_line_id.name', '=', line), ('production_id.state', 'in', ['scrap', 'cancel']), 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, '>=', date.strftime('%Y-%m-%d 00:00:00')),
(date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00')) (date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00'))
]) ])
@@ -750,11 +768,14 @@ class Sf_Dashboard_Connect(http.Controller):
for line in line_list: for line in line_list:
# 未完成订单 # 未完成订单
not_done_orders = plan_obj.search( # not_done_orders = plan_obj.search(
[('production_line_id.name', '=', line), ('state', 'not in', ['finished']), # [('production_line_id.name', '=', line), ('state', 'not in', ['finished']),
('production_id.state', 'not in', ['cancel', 'done']), ('active', '=', True) # ('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小时前的时间 # 获取当前时间并计算24小时前的时间
@@ -806,16 +827,18 @@ class Sf_Dashboard_Connect(http.Controller):
'draft': '待排程', 'draft': '待排程',
'done': '已排程', 'done': '已排程',
'processing': '生产中', 'processing': '生产中',
'finished': '已完成' 'finished': '已完成',
'ready': '待加工',
'progress': '生产中',
} }
line_dict = { line_dict = {
'sequence': id_to_sequence[order.id], 'sequence': id_to_sequence[order.id],
'workorder_name': order.name, 'workorder_name': order.production_id.name,
'blank_name': blank_name, 'blank_name': blank_name,
'material': material, 'material': material,
'dimensions': dimensions, 'dimensions': dimensions,
'order_qty': order.product_qty, 'order_qty': 1,
'state': state_dict[order.state], 'state': state_dict[order.state],
} }
@@ -896,15 +919,17 @@ class Sf_Dashboard_Connect(http.Controller):
cur.execute(sql2, (item,)) cur.execute(sql2, (item,))
result2 = cur.fetchall() result2 = cur.fetchall()
# print('result2========', result2)
#
for row in result: for row in result:
res['data'][item] = {'idle_count': row[0]} res['data'][item] = {'idle_count': row[0]}
alarm_count = [] alarm_count = []
for row in result2: for row in result2:
alarm_count.append(row[1]) alarm_count.append(row[1])
if row[0]: 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: else:
total_alarm_time += 0.0 total_alarm_time += 0.0
if len(list(set(alarm_count))) == 1: if len(list(set(alarm_count))) == 1:
@@ -914,6 +939,7 @@ class Sf_Dashboard_Connect(http.Controller):
alarm_count_num = 1 alarm_count_num = 1
else: else:
alarm_count_num = len(list(set(alarm_count))) alarm_count_num = len(list(set(alarm_count)))
res['data'][item]['total_alarm_time'] = total_alarm_time / 3600 res['data'][item]['total_alarm_time'] = total_alarm_time / 3600
res['data'][item]['alarm_count_num'] = alarm_count_num res['data'][item]['alarm_count_num'] = alarm_count_num
@@ -941,7 +967,7 @@ class Sf_Dashboard_Connect(http.Controller):
# machine_list = ast.literal_eval(kw['machine_list']) # machine_list = ast.literal_eval(kw['machine_list'])
# for item in machine_list: # for item in machine_list:
# machine_data = equipment_obj.search([('code', '=', item)]) # machine_data = equipment_obj.search([('code', '=', item)])
for log in maintenance_logs_obj.search([]): for log in maintenance_logs_obj.search([], order='id desc', limit=30):
res['data'].append({ res['data'].append({
'name': log.name, 'name': log.name,
'alarm_time': log.alarm_time.strftime('%Y-%m-%d %H:%M:%S'), 'alarm_time': log.alarm_time.strftime('%Y-%m-%d %H:%M:%S'),
@@ -1129,8 +1155,9 @@ class Sf_Dashboard_Connect(http.Controller):
cur.execute(""" cur.execute("""
SELECT * FROM device_data SELECT * FROM device_data
WHERE device_name = %s WHERE device_name = %s
AND time::date = CURRENT_DATE AND time >= CURRENT_DATE -- 今日 00:00:00
AND device_state != '离线' AND time < CURRENT_DATE + 1 -- 明日 00:00:00
AND device_state in ('待机', '警告', '运行中')
ORDER BY time ASC ORDER BY time ASC
LIMIT 1; LIMIT 1;
""", (item,)) """, (item,))
@@ -1142,8 +1169,9 @@ class Sf_Dashboard_Connect(http.Controller):
cur.execute(""" cur.execute("""
SELECT * FROM device_data SELECT * FROM device_data
WHERE device_name = %s WHERE device_name = %s
AND time::date = CURRENT_DATE AND time >= CURRENT_DATE -- 今日 00:00:00
AND device_state != '离线' AND time < CURRENT_DATE + 1 -- 明日 00:00:00
AND device_state in ('待机', '警告', '运行中')
ORDER BY time DESC ORDER BY time DESC
LIMIT 1; LIMIT 1;
""", (item,)) """, (item,))
@@ -1163,23 +1191,23 @@ class Sf_Dashboard_Connect(http.Controller):
cur.execute(""" cur.execute("""
SELECT * FROM device_data SELECT * FROM device_data
WHERE device_name = %s WHERE device_name = %s
AND EXTRACT(YEAR FROM time) = EXTRACT(YEAR FROM CURRENT_DATE) AND time >= DATE_TRUNC('MONTH', CURRENT_DATE)
AND EXTRACT(MONTH FROM time) = EXTRACT(MONTH FROM CURRENT_DATE) AND time < DATE_TRUNC('MONTH', CURRENT_DATE) + INTERVAL '1 MONTH'
AND device_state != '离线' AND device_state in ('待机', '警告', '运行中')
ORDER BY time ASC ORDER BY time ASC
LIMIT 1; LIMIT 1;
""", (item,)) """, (item,))
first_month = fetch_result_as_dict(cur) first_month = fetch_result_as_dict(cur)
# print("当月第一条记录(非离线):", first_month) # print("当月第一条记录:", first_month)
# 获取当月最新一条记录排除device_state等于离线的记录 # 获取当月最新一条记录
with conn.cursor() as cur: with conn.cursor() as cur:
cur.execute(""" cur.execute("""
SELECT * FROM device_data SELECT * FROM device_data
WHERE device_name = %s WHERE device_name = %s
AND EXTRACT(YEAR FROM time) = EXTRACT(YEAR FROM CURRENT_DATE) AND time >= DATE_TRUNC('MONTH', CURRENT_DATE)
AND EXTRACT(MONTH FROM time) = EXTRACT(MONTH FROM CURRENT_DATE) AND time < DATE_TRUNC('MONTH', CURRENT_DATE) + INTERVAL '1 MONTH'
AND device_state != '离线' AND device_state in ('待机', '警告', '运行中')
ORDER BY time DESC ORDER BY time DESC
LIMIT 1; LIMIT 1;
""", (item,)) """, (item,))
@@ -1200,7 +1228,7 @@ class Sf_Dashboard_Connect(http.Controller):
cur.execute(""" cur.execute("""
SELECT * FROM device_data SELECT * FROM device_data
WHERE device_name = %s WHERE device_name = %s
AND device_state != '离线' AND device_state in ('待机', '警告', '运行中')
ORDER BY time ASC ORDER BY time ASC
LIMIT 1; LIMIT 1;
""", (item,)) """, (item,))
@@ -1212,7 +1240,7 @@ class Sf_Dashboard_Connect(http.Controller):
cur.execute(""" cur.execute("""
SELECT * FROM device_data SELECT * FROM device_data
WHERE device_name = %s WHERE device_name = %s
AND device_state != '离线' AND device_state in ('待机', '警告', '运行中')
ORDER BY time DESC ORDER BY time DESC
LIMIT 1; LIMIT 1;
""", (item,)) """, (item,))
@@ -1290,7 +1318,7 @@ class Sf_Dashboard_Connect(http.Controller):
cur.execute(""" cur.execute("""
SELECT * FROM device_data SELECT * FROM device_data
WHERE device_name = %s WHERE device_name = %s
AND device_state != '离线' AND process_time IS NOT NULL AND device_state in ('待机', '警告', '运行中') AND process_time IS NOT NULL
ORDER BY time DESC ORDER BY time DESC
LIMIT 1; LIMIT 1;
""", (item,)) """, (item,))
@@ -1299,7 +1327,7 @@ class Sf_Dashboard_Connect(http.Controller):
cur.execute(""" cur.execute("""
SELECT * FROM device_data SELECT * FROM device_data
WHERE device_name = %s WHERE device_name = %s
AND device_state != '离线' AND time >= %s AND process_time IS NOT NULL AND device_state in ('待机', '警告', '运行中') AND time >= %s AND process_time IS NOT NULL
ORDER BY time ASC ORDER BY time ASC
LIMIT 1; LIMIT 1;
""", (item, time_threshold)) """, (item, time_threshold))
@@ -1329,7 +1357,7 @@ class Sf_Dashboard_Connect(http.Controller):
for result in results: for result in results:
alarm_last_24_nums.append(result[1]) alarm_last_24_nums.append(result[1])
if result[0]: if result[0]:
if float(result[0]) >= 1000: if float(result[0]) >= 28800:
continue continue
alarm_last_24_time += float(result[0]) alarm_last_24_time += float(result[0])
else: else:
@@ -1347,7 +1375,7 @@ class Sf_Dashboard_Connect(http.Controller):
for result in results: for result in results:
alarm_all_nums.append(result[1]) alarm_all_nums.append(result[1])
if result[0]: if result[0]:
if float(result[0]) >= 1000: if float(result[0]) >= 28800:
continue continue
alarm_all_time += float(result[0]) alarm_all_time += float(result[0])
else: else:
@@ -1382,3 +1410,207 @@ class Sf_Dashboard_Connect(http.Controller):
conn.close() conn.close()
return json.dumps(res) 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_data = []
month_data = []
today_check_ng = []
month_check_ng = []
total_alarm_time = 0
today_alarm_time = 0
month_alarm_time = 0
for order in orders:
time = datetime.strptime(order.date_finished, "%Y-%m-%d %H:%M:%S")
if time >= today_start:
today_data.append(order)
if time >= month_start:
month_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()
print(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 += 0
print(total_power_on_time, month_power_on_time, today_power_on_time)
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)
for today in today_data:
if today[0]:
if float(today[0]) >= 28800:
continue
today_alarm_time += float(today[0])
else:
today_alarm_time += 0.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()
print('报警时间=============', total_alarm_time, month_alarm_time, today_alarm_time)
logging.info("报警时间=%s" % total_alarm_time)
logging.info("报警时间=%s" % month_alarm_time)
logging.info("报警时间=%s" % 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) / 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) / today_power_on_time
else:
today_power_on_rate = 0
print("总开动率: %s" % total_power_on_rate)
print("月开动率: %s" % month_power_on_rate)
print("日开动率: %s" % today_power_on_rate)
# 计算性能开动率(累计、月、日)
print('===========',orders)
print(len(orders))
total_performance_rate = len(orders) * 30 * 60 / (total_power_on_time - total_alarm_time)
month_performance_rate = len(month_data) * 30 * 60 / (month_power_on_time - month_alarm_time)
today_performance_rate = len(today_data) * 30 * 60 / (today_power_on_time - today_alarm_time) if today_power_on_time != 0 else 0
print("总性能率: %s" % total_performance_rate)
print("月性能率: %s" % month_performance_rate)
print("日性能率: %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_data) - len(month_check_ng)) / len(month_data) if len(month_data) != 0 else 0
today_pass_rate = (len(today_data) - len(today_check_ng)) / len(today_data) if len(today_data) != 0 else 0
print("总合格率: %s" % total_pass_rate)
print("月合格率: %s" % month_pass_rate)
print("日合格率: %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,
# }
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,
}
return json.dumps(res)

View File

@@ -7,7 +7,7 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//page[last()-3]" position="before"> <xpath expr="//page[last()-3]" position="before">
<!-- <page string="下发记录" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'>--> <!-- <page string="下发记录" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'>-->
<page string="下发记录" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'> <page string="下发记录" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "HDR")]}'>
<field name="delivery_records"> <field name="delivery_records">
<tree create="false"> <tree create="false">
<field name="delivery_type"/> <field name="delivery_type"/>

View File

@@ -6,7 +6,7 @@
<field name="inherit_id" ref="mrp.mrp_production_workorder_form_view_inherit"/> <field name="inherit_id" ref="mrp.mrp_production_workorder_form_view_inherit"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//page[last()-3]" position="before"> <xpath expr="//page[last()-3]" position="before">
<page string="机床信息" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'> <page string="机床信息" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "MTI")]}'>
<group string="机床信息"> <group string="机床信息">
<group> <group>
<field name="machine_tool_name"/> <field name="machine_tool_name"/>

View File

@@ -27,7 +27,8 @@ class JikimoSaleRoutePicking(Sf_Bf_Connect):
bfm_process_order_list = json.loads(kw['bfm_process_order_list']) bfm_process_order_list = json.loads(kw['bfm_process_order_list'])
order_id = request.env['sale.order'].with_user(request.env.ref("base.user_admin")).sale_order_create( order_id = request.env['sale.order'].with_user(request.env.ref("base.user_admin")).sale_order_create(
company_id, kw['delivery_name'], kw['delivery_telephone'], kw['delivery_address'], company_id, kw['delivery_name'], kw['delivery_telephone'], kw['delivery_address'],
kw['delivery_end_date'], kw['payments_way'], kw['pay_way'], kw['order_number'], state='draft') kw['delivery_end_date'], kw['payments_way'], kw['pay_way'], kw['order_number'], state='draft',
model_display_version=kw.get('model_display_version'))
i = 1 i = 1
# 给sale_order的default_code字段赋值 # 给sale_order的default_code字段赋值
# aa = request.env['sale.order'].sudo().search([('name', '=', order_id.name)]) # aa = request.env['sale.order'].sudo().search([('name', '=', order_id.name)])
@@ -45,6 +46,7 @@ class JikimoSaleRoutePicking(Sf_Bf_Connect):
order_id.with_user(request.env.ref("base.user_admin")).sale_order_create_line(product, item) order_id.with_user(request.env.ref("base.user_admin")).sale_order_create_line(product, item)
i += 1 i += 1
res['factory_order_no'] = order_id.name res['factory_order_no'] = order_id.name
order_id.confirm_to_supply_method()
except Exception as e: except Exception as e:
traceback_error = traceback.format_exc() traceback_error = traceback.format_exc()
logging.error('get_bfm_process_order_list error: %s' % traceback_error) logging.error('get_bfm_process_order_list error: %s' % traceback_error)

View File

@@ -4,5 +4,63 @@
<field name="code">PTD</field> <field name="code">PTD</field>
<field name="name">后置三元检测</field> <field name="name">后置三元检测</field>
</record> </record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_2">
<field name="code">WCP</field>
<field name="name">工件装夹</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>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_6">
<field name="code">WD</field>
<field name="name">工件配送</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>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_10">
<field name="code">CMM_P</field>
<field name="name">CMM程序</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>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_14">
<field name="code">DCP</field>
<field name="name">解除装夹</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_15">
<field name="code">CMR</field>
<field name="name">开料要求</field>
</record>
<!-- 原生页签先不进行配置 -->
<!-- <record model="sf.work.individuation.page" id="sf_work_individuation_page_7">-->
<!-- <field name="code">ML</field>-->
<!-- <field name="name">物料</field>-->
<!-- </record>-->
<!-- <record model="sf.work.individuation.page" id="sf_work_individuation_page_8">-->
<!-- <field name="code">TT</field>-->
<!-- <field name="name">时间跟踪</field>-->
<!-- </record>-->
</data> </data>
</odoo> </odoo>

View File

@@ -87,11 +87,12 @@ class AgvScheduling(models.Model):
agv_route_type: AGV任务类型 agv_route_type: AGV任务类型
workorders: 工单 workorders: 工单
""" """
scheduling = None
_logger.info('创建AGV调度任务\r\n起点为【%s】,任务类型为【%s】,工单为【%s' % (agv_start_site_name, agv_route_type, workorders)) _logger.info('创建AGV调度任务\r\n起点为【%s】,任务类型为【%s】,工单为【%s' % (agv_start_site_name, agv_route_type, workorders))
if not workorders: if not workorders:
raise UserError(_('工单不能为空')) raise UserError(_('工单不能为空'))
agv_start_site = self.env['sf.agv.site'].sudo().search([('name', '=', agv_start_site_name)], limit=1) agv_start_sites = self.env['sf.agv.site'].sudo().search([('name', '=', agv_start_site_name)])
if not agv_start_site: if not agv_start_sites:
raise UserError(_('不存在名称为【%s】的接驳站,请先创建!' % agv_start_site_name)) raise UserError(_('不存在名称为【%s】的接驳站,请先创建!' % agv_start_site_name))
# 如果存在相同任务类型工单的AGV调度任务则提示错误 # 如果存在相同任务类型工单的AGV调度任务则提示错误
agv_scheduling = self.sudo().search([ agv_scheduling = self.sudo().search([
@@ -107,24 +108,32 @@ class AgvScheduling(models.Model):
(','.join(repetitive_workorders.mapped('production_id.name')), agv_scheduling.name) (','.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 = { vals = {
'start_site_id': agv_start_site.id,
'agv_route_type': agv_route_type, 'agv_route_type': agv_route_type,
'workorder_ids': workorders.ids, 'workorder_ids': workorders.ids,
# 'workpiece_delivery_ids': deliveries.mapped('id') if deliveries else [], # 'workpiece_delivery_ids': deliveries.mapped('id') if deliveries else [],
'task_create_time': fields.Datetime.now() '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: if not agv_routes:
raise UserError(_('不存在起点为【%s】的【%s】任务路线,请先创建!' % (agv_start_site_name, agv_route_type))) 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 idle_route = None
if len(agv_routes) == 1: if len(agv_routes) == 1:
idle_route = agv_routes[0] 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: else:
# 判断终点接驳站是否为空闲 # 判断终点接驳站是否为空闲
idle_routes = agv_routes.filtered(lambda r: r.end_site_id.state == '空闲') 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_routes = sorted(idle_routes, key=lambda r: r.end_site_id.name)
idle_route = idle_routes[0] 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: try:
scheduling = self.env['sf.agv.scheduling'].sudo().create(vals) scheduling = self.env['sf.agv.scheduling'].sudo().create(vals)
# 触发空闲接驳站状态更新,触发新任务下发 # 触发空闲接驳站状态更新,触发新任务下发
@@ -142,7 +154,7 @@ class AgvScheduling(models.Model):
except Exception as e: except Exception as e:
_logger.error('添加AGV调度任务失败: %s', e) _logger.error('添加AGV调度任务失败: %s', e)
raise UserError(_('添加AGV调度任务失败: %s', e)) raise UserError(_('添加AGV调度任务失败: %s', e))
return scheduling return scheduling
def on_site_state_change(self, agv_site_id, agv_site_state): def on_site_state_change(self, agv_site_id, agv_site_state):

View File

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

View File

@@ -5,6 +5,8 @@ import logging
import json import json
import os import os
import re import re
import traceback
import requests import requests
from itertools import groupby from itertools import groupby
from collections import defaultdict, namedtuple from collections import defaultdict, namedtuple
@@ -25,6 +27,7 @@ class MrpProduction(models.Model):
maintenance_count = fields.Integer(compute='_compute_maintenance_count', string="Number of maintenance requests") maintenance_count = fields.Integer(compute='_compute_maintenance_count', string="Number of maintenance requests")
request_ids = fields.One2many('maintenance.request', 'production_id') request_ids = fields.One2many('maintenance.request', 'production_id')
model_file = fields.Binary('模型文件', related='product_id.model_file') model_file = fields.Binary('模型文件', related='product_id.model_file')
glb_url = fields.Char('模型文件', related='product_id.glb_url')
schedule_state = fields.Selection([('未排', '未排'), ('已排', '已排'), ('已完成', '已完成')], schedule_state = fields.Selection([('未排', '未排'), ('已排', '已排'), ('已完成', '已完成')],
string='排程状态', default='未排') string='排程状态', default='未排')
work_order_state = fields.Selection([('未排', '未排'), ('已排', '已排'), ('已完成', '已完成')], work_order_state = fields.Selection([('未排', '未排'), ('已排', '已排'), ('已完成', '已完成')],
@@ -256,14 +259,46 @@ class MrpProduction(models.Model):
], string='工序状态', default='待装夹') ], string='工序状态', default='待装夹')
# 零件图号 # 零件图号
part_number = fields.Char('零件图号', related='product_id.part_number', readonly=True) part_number = fields.Char('零件图号', compute='_compute_part_info', store=True)
# 上传零件图纸 # 上传零件图纸
part_drawing = fields.Binary('零件图纸', related='product_id.machining_drawings', readonly=True) part_drawing = fields.Binary('零件图纸', related='product_id.machining_drawings', readonly=True)
quality_standard = fields.Binary('质检标准', related='product_id.quality_standard', readonly=True) quality_standard = fields.Binary('质检标准', related='product_id.quality_standard', readonly=True)
part_name = fields.Char(string='零件名称', related='product_id.part_name', readonly=True) part_name = fields.Char(string='零件名称', compute='_compute_part_info', store=True)
@api.depends('product_id')
def _compute_part_info(self):
try:
for production_id in self:
if production_id.product_id.categ_id.type == '成品':
production_id.part_number = production_id.product_id.part_number
production_id.part_name = production_id.product_id.part_name
elif production_id.product_id.categ_id.type == '坯料':
product_name = ''
match = re.search(r'(S\d{5}-\d)', production_id.product_id.name)
# 如果匹配成功,提取结果
if match:
product_name = match.group(0)
if production_id.sale_order_id:
sale_order = production_id.sale_order_id
else:
sale_order_name = ''
match = re.search(r'(S\d+)', production_id.product_id.name)
if match:
sale_order_name = match.group(0)
sale_order = self.env['sale.order'].sudo().search(
[('name', '=', sale_order_name)])
logging.info("product_name is :%s" % product_name)
filtered_order_line = sale_order.order_line.filtered(
lambda production: re.search(f'{product_name}$', production.product_id.name)
)
if filtered_order_line:
production_id.part_number = filtered_order_line.part_number
production_id.part_name = filtered_order_line.part_name
except Exception as e:
traceback_error = traceback.format_exc()
logging.error("制造订单零件图号 零件名称获取失败:%s" % traceback_error)
# 判断制造的产品类型 # 判断制造的产品类型
production_product_type = fields.Selection([ production_product_type = fields.Selection([
@@ -365,7 +400,7 @@ class MrpProduction(models.Model):
and production.schedule_state == '已排' and production.is_rework is False): and production.schedule_state == '已排' and production.is_rework is False):
production.state = 'pending_cam' production.state = 'pending_cam'
if any((wo.test_results == '返工' and wo.state == 'done' and if any((wo.test_results == '返工' and wo.state == 'done' and
(production.programming_state in ['已编程'] or wo.individuation_page_PTD is True)) (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 ['编程中', '已编程']) 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: for wo in production.workorder_ids) or production.is_rework is True:
production.state = 'rework' production.state = 'rework'
@@ -684,7 +719,6 @@ class MrpProduction(models.Model):
logging.info('change_programming_state error:%s' % e) logging.info('change_programming_state error:%s' % e)
raise UserError("修改编程单状态失败,请联系管理员") raise UserError("修改编程单状态失败,请联系管理员")
# cnc程序获取 # cnc程序获取
def fetchCNC(self, production_names): def fetchCNC(self, production_names):
cnc = self.env['mrp.production'].search([('id', '=', self.id)]) cnc = self.env['mrp.production'].search([('id', '=', self.id)])
@@ -714,21 +748,23 @@ class MrpProduction(models.Model):
[('id', '=', cnc.product_id.materials_type_id.id)]).materials_no, [('id', '=', cnc.product_id.materials_type_id.id)]).materials_no,
'machining_processing_panel': cnc.product_id.model_processing_panel, 'machining_processing_panel': cnc.product_id.model_processing_panel,
'machining_precision': '', 'machining_precision': '',
'embryo_long': cnc.product_id.bom_ids.bom_line_ids.product_id.length, 'embryo_long': cnc.product_id.bom_ids[0].bom_line_ids.product_id.length,
'embryo_height': cnc.product_id.bom_ids.bom_line_ids.product_id.height, 'embryo_height': cnc.product_id.bom_ids[0].bom_line_ids.product_id.height,
'embryo_width': cnc.product_id.bom_ids.bom_line_ids.product_id.width, 'embryo_width': cnc.product_id.bom_ids[0].bom_line_ids.product_id.width,
'order_no': cnc.origin, 'order_no': cnc.origin,
'model_order_no': cnc.product_id.default_code, 'model_order_no': cnc.product_id.default_code,
'user': cnc.env.user.name, 'user': cnc.env.user.name,
'programme_way': programme_way, 'programme_way': programme_way,
'model_file': '' if not cnc.product_id.model_file else base64.b64encode( # 'model_file': '' if not cnc.product_id.model_file else base64.b64encode(
cnc.product_id.model_file).decode('utf-8'), # cnc.product_id.model_file).decode('utf-8'),
# 'glb_url': cnc.product_id.glb_url,
'part_name': cnc.product_id.part_name, 'part_name': cnc.product_id.part_name,
'part_number': cnc.product_id.part_number, 'part_number': cnc.product_id.part_number,
'machining_drawings': base64.b64encode(cnc.product_id.machining_drawings).decode( 'machining_drawings': base64.b64encode(cnc.product_id.machining_drawings).decode(
'utf-8') if cnc.product_id.machining_drawings else '', 'utf-8') if cnc.product_id.machining_drawings else '',
'machining_drawings_name': cnc.product_id.machining_drawings_name, 'machining_drawings_name': cnc.product_id.machining_drawings_name,
'machining_drawings_mimetype': cnc.product_id.machining_drawings_mimetype, 'machining_drawings_mimetype': cnc.product_id.machining_drawings_mimetype,
# 'model_id': cnc.product_id.model_id,
} }
# 打印出除了 model_file 之外的所有键值对 # 打印出除了 model_file 之外的所有键值对
for key, value in res.items(): for key, value in res.items():
@@ -1298,12 +1334,14 @@ class MrpProduction(models.Model):
'target': 'new', 'target': 'new',
'context': { 'context': {
'default_production_id': self.id, 'default_production_id': self.id,
'default_is_clamping': True if self.workorder_ids.filtered(lambda wk: wk.routing_type == '装夹预调') else False, 'default_is_clamping': True if self.workorder_ids.filtered(
lambda wk: wk.routing_type == '装夹预调') else False,
'default_workorder_ids': workorder_ids.ids if workorder_ids.ids != [] else self.workorder_ids.ids, 'default_workorder_ids': workorder_ids.ids if workorder_ids.ids != [] else self.workorder_ids.ids,
'default_hidden_workorder_ids': ','.join(map(str, work_id_list)) if work_id_list != [] else '', 'default_hidden_workorder_ids': ','.join(map(str, work_id_list)) if work_id_list != [] else '',
'default_reprogramming_num': cloud_programming.get('reprogramming_num') if cloud_programming else '', '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_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 'default_is_reprogramming': True if cloud_programming and (
cloud_programming.get('programming_state') in ['已下发']) else False
} }
} }
@@ -1337,7 +1375,8 @@ class MrpProduction(models.Model):
for rework_item in rework_workorder: for rework_item in rework_workorder:
pending_workorder = production.workorder_ids.filtered( pending_workorder = production.workorder_ids.filtered(
lambda m1: m1.state in [ lambda m1: m1.state in [
'pending'] and m1.processing_panel == rework_item.processing_panel and m1.routing_type in ['CNC加工', '人工线下加工']) 'pending'] and m1.processing_panel == rework_item.processing_panel and m1.routing_type in [
'CNC加工', '人工线下加工'])
if not pending_workorder.cnc_ids: if not pending_workorder.cnc_ids:
production.get_new_program(rework_item.processing_panel) production.get_new_program(rework_item.processing_panel)
# production.write({'state': 'progress', 'programming_state': '已编程', 'is_rework': False}) # production.write({'state': 'progress', 'programming_state': '已编程', 'is_rework': False})
@@ -1380,8 +1419,9 @@ class MrpProduction(models.Model):
if productions: if productions:
for production in productions: for production in productions:
panel_workorder = production.workorder_ids.filtered(lambda panel_workorder = production.workorder_ids.filtered(lambda
pw: pw.processing_panel == processing_panel and pw.routing_type in ['CNC加工', '人工线下加工'] and pw.state not in ( pw: pw.processing_panel == processing_panel and pw.routing_type in [
'rework', 'done')) 'CNC加工', '人工线下加工'] and pw.state not in (
'rework', 'done'))
if panel_workorder: if panel_workorder:
if panel_workorder.cmm_ids: if panel_workorder.cmm_ids:
panel_workorder.cmm_ids.sudo().unlink() panel_workorder.cmm_ids.sudo().unlink()
@@ -1406,8 +1446,9 @@ class MrpProduction(models.Model):
'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())}) 'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
logging.info('len(cnc_worksheet):%s' % len(panel_workorder.cnc_worksheet)) logging.info('len(cnc_worksheet):%s' % len(panel_workorder.cnc_worksheet))
pre_workorder = production.workorder_ids.filtered(lambda pre_workorder = production.workorder_ids.filtered(lambda
ap: ap.routing_type in ['装夹预调', '人工线下加工'] and ap.processing_panel == processing_panel and ap.state not in ( ap: ap.routing_type in ['装夹预调',
'rework', 'done')) '人工线下加工'] and ap.processing_panel == processing_panel and ap.state not in (
'rework', 'done'))
if pre_workorder: if pre_workorder:
pre_workorder.write( pre_workorder.write(
{'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())}) {'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())})
@@ -1570,7 +1611,7 @@ class MrpProduction(models.Model):
vals['picking_type_id'] = picking_type_id vals['picking_type_id'] = picking_type_id
vals['name'] = self.env['stock.picking.type'].browse(picking_type_id).sequence_id.next_by_id() vals['name'] = self.env['stock.picking.type'].browse(picking_type_id).sequence_id.next_by_id()
product_id = self.env['product.product'].browse(vals['product_id']) product_id = self.env['product.product'].browse(vals['product_id'])
is_self_process = product_id.materials_type_id and product_id.materials_type_id.gain_way and product_id.materials_type_id.gain_way != '自加工' is_self_process = product_id.materials_type_id.gain_way if product_id.materials_type_id else None
is_customer_provided = product_id.is_customer_provided is_customer_provided = product_id.is_customer_provided
key = f"{is_self_process}_{is_customer_provided}" key = f"{is_self_process}_{is_customer_provided}"
if not is_custemer_group_id.get(key): if not is_custemer_group_id.get(key):

View File

@@ -289,6 +289,7 @@ class ResMrpWorkOrder(models.Model):
cmm_ids = fields.One2many("sf.cmm.program", 'workorder_id', string="CMM程序") cmm_ids = fields.One2many("sf.cmm.program", 'workorder_id', string="CMM程序")
tray_code = fields.Char(string="托盘编码") tray_code = fields.Char(string="托盘编码")
glb_file = fields.Binary("glb模型文件", related='production_id.model_file') glb_file = fields.Binary("glb模型文件", related='production_id.model_file')
glb_url = fields.Char("glb模型文件", related='production_id.glb_url')
is_subcontract = fields.Boolean(string='是否外协') is_subcontract = fields.Boolean(string='是否外协')
surface_technics_parameters_id = fields.Many2one('sf.production.process.parameter', string="表面工艺可选参数") surface_technics_parameters_id = fields.Many2one('sf.production.process.parameter', string="表面工艺可选参数")
@@ -735,7 +736,8 @@ class ResMrpWorkOrder(models.Model):
local_filename = self.save_name + '.xls' local_filename = self.save_name + '.xls'
local_file_path = os.path.join(local_dir_path, local_filename) local_file_path = os.path.join(local_dir_path, local_filename)
logging.info('local_file_path:%s' % local_file_path) logging.info('local_file_path:%s' % local_file_path)
remote_path = '/home/ftp/ftp_root/ThreeTest/XT/Before/' + local_filename # remote_path = '/home/ftp/ftp_root/ThreeTest/XT/Before/' + local_filename
remote_path = '/ThreeTest/XT/Before/' + local_filename
logging.info('remote_path:%s' % remote_path) logging.info('remote_path:%s' % remote_path)
is_get_detection_file = self.env['ir.config_parameter'].sudo().get_param('is_get_detection_file') is_get_detection_file = self.env['ir.config_parameter'].sudo().get_param('is_get_detection_file')
if not is_get_detection_file: if not is_get_detection_file:
@@ -1198,11 +1200,7 @@ class ResMrpWorkOrder(models.Model):
'cmm_ids': production.workorder_ids.filtered(lambda t: t.routing_type == 'CNC加工').cmm_ids, 'cmm_ids': production.workorder_ids.filtered(lambda t: t.routing_type == 'CNC加工').cmm_ids,
}] }]
return workorders_values_str return workorders_values_str
def _process_compute_state(self):
@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')
def _compute_state(self):
for workorder in self: for workorder in self:
# 如果工单的工序没有进行排序则跳出循环 # 如果工单的工序没有进行排序则跳出循环
if workorder.production_id.workorder_ids.filtered(lambda wk: wk.sequence == 0): if workorder.production_id.workorder_ids.filtered(lambda wk: wk.sequence == 0):
@@ -1289,7 +1287,20 @@ class ResMrpWorkOrder(models.Model):
mo.get_move_line(workorder.production_id, workorder)) mo.get_move_line(workorder.production_id, workorder))
else: else:
workorder.state = 'waiting' 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')
def _compute_state(self):
self._process_compute_state()
for workorder in self:
if workorder.state == 'waiting' or workorder.state == 'pending':
for check_id in workorder.check_ids:
if not check_id.is_inspect:
check_id.quality_state = 'waiting'
if workorder.state == 'ready':
for check_id in workorder.check_ids:
if not check_id.is_inspect:
check_id.quality_state = 'none'
# 重写工单开始按钮方法 # 重写工单开始按钮方法
def button_start(self): def button_start(self):
# 判断工单状态是否为等待组件 # 判断工单状态是否为等待组件
@@ -1442,7 +1453,8 @@ class ResMrpWorkOrder(models.Model):
record.production_id.process_state = '待加工' record.production_id.process_state = '待加工'
# 生成工件配送单 # 生成工件配送单
record.workpiece_delivery_ids = record._json_workpiece_delivery_list() record.workpiece_delivery_ids = record._json_workpiece_delivery_list()
if record.routing_type == 'CNC加工' or record.individuation_page_PTD is True: if (record.routing_type == 'CNC加工' or
(record.individuation_page_list and 'PTD' in record.individuation_page_list)):
if record.routing_type == 'CNC加工': if record.routing_type == 'CNC加工':
record.process_state = '待解除装夹' record.process_state = '待解除装夹'
# record.write({'process_state': '待加工'}) # record.write({'process_state': '待加工'})
@@ -1509,8 +1521,12 @@ class ResMrpWorkOrder(models.Model):
for workorder in record.production_id.workorder_ids: for workorder in record.production_id.workorder_ids:
if workorder.processing_panel == record.processing_panel: if workorder.processing_panel == record.processing_panel:
rfid_code = workorder.rfid_code rfid_code = workorder.rfid_code
workorder.write({'rfid_code_old': rfid_code, if record.is_rework is not True:
'rfid_code': False}) workorder.write({'rfid_code_old': rfid_code, 'rfid_code': False})
elif workorder.routing_type != '装夹预调' and workorder.state != 'rework':
workorder.write({'rfid_code_old': False, 'rfid_code': False})
elif workorder.routing_type == '装夹预调' and workorder.state != 'rework':
workorder.write({'rfid_code_old': rfid_code, 'rfid_code': False})
self.env['stock.lot'].sudo().search([('rfid', '=', rfid_code)]).write( self.env['stock.lot'].sudo().search([('rfid', '=', rfid_code)]).write(
{'tool_material_status': '可用'}) {'tool_material_status': '可用'})
if workorder.rfid_code: if workorder.rfid_code:
@@ -1518,7 +1534,7 @@ class ResMrpWorkOrder(models.Model):
# workorder.rfid_code_old = rfid_code # workorder.rfid_code_old = rfid_code
# workorder.rfid_code = False # workorder.rfid_code = False
logging.info('workorder.rfid_code:%s' % workorder.rfid_code) logging.info('workorder.rfid_code:%s' % workorder.rfid_code)
# if is_production_id is True and record.routing_type in ['解除装夹', '表面工艺', '切割']:
if is_production_id is True: if is_production_id is True:
logging.info('product_qty:%s' % record.production_id.product_qty) logging.info('product_qty:%s' % record.production_id.product_qty)
for move_raw_id in record.production_id.move_raw_ids: for move_raw_id in record.production_id.move_raw_ids:
@@ -1533,6 +1549,17 @@ class ResMrpWorkOrder(models.Model):
# if raw_move: # if raw_move:
# raw_move.write({'state': 'done'}) # raw_move.write({'state': 'done'})
if record.production_id.state != 'rework': if record.production_id.state != 'rework':
# 如果工单包含了外协工序,需要预留数量
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)
if quant.reserved_quantity == 0:
self.env['stock.quant']._update_reserved_quantity(
self.move_raw_ids.product_id,
location_id,
quant.quantity,
lot_id=quant.lot_id,
)
record.production_id.button_mark_done1() record.production_id.button_mark_done1()
# record.production_id.state = 'done' # record.production_id.state = 'done'
@@ -1675,31 +1702,31 @@ class ResMrpWorkOrder(models.Model):
move_subcontract_workorder_ids = fields.One2many('stock.move', 'subcontract_workorder_id', string='组件') move_subcontract_workorder_ids = fields.One2many('stock.move', 'subcontract_workorder_id', string='组件')
# ==============================配置化页签--个性化记录=================================== # ==============================配置化页签--个性化记录===================================
routing_workcenter_id = fields.Many2one('mrp.routing.workcenter', compute='_compute_routing_workcenter_id', routing_work_center_id = fields.Many2one('mrp.routing.workcenter', compute='_compute_routing_work_center_id',
store=True) store=True, string='工序作业')
individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录', store=True, individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录',
compute='_compute_individuation_page_ids') related='routing_work_center_id.individuation_page_ids')
individuation_page_PTD = fields.Boolean('个性化记录(是否显示后置三元检测[PTD]页签)', default=False) individuation_page_list = fields.Char('个性化记录', default='', compute='_compute_individuation_page_ids', store=True)
@api.depends('name') @api.depends('name')
def _compute_routing_workcenter_id(self): def _compute_routing_work_center_id(self):
for mw in self: for mw in self:
routing_workcenter_id = self.env['mrp.routing.workcenter'].sudo().search( if not mw.routing_work_center_id and mw.name:
[('name', '=', mw.name), ('routing_type', '=', mw.routing_type)]) routing_work_center_id = self.env['mrp.routing.workcenter'].sudo().search(
if routing_workcenter_id: [('name', 'in', mw.name.split('-')), ('routing_type', '=', mw.routing_type)])
mw.routing_workcenter_id = routing_workcenter_id.id if routing_work_center_id:
mw.routing_work_center_id = routing_work_center_id.id
@api.depends('routing_workcenter_id.individuation_page_ids') @api.depends('individuation_page_ids')
def _compute_individuation_page_ids(self): def _compute_individuation_page_ids(self):
for mw in self: for mw in self:
if mw.routing_workcenter_id: mw.individuation_page_list = '[]'
mw.individuation_page_ids = mw.routing_workcenter_id.individuation_page_ids.ids if mw.routing_work_center_id:
# 初始化页签配置 if mw.individuation_page_ids:
mw.individuation_page_PTD = False # 根据工单对应的【作业_个性化记录】配置页签
# 根据工单对应的【作业_个性化记录】配置页签 individuation_page_list = [item.code for item in mw.individuation_page_ids]
if any(item.code == 'PTD' for item in mw.routing_workcenter_id.individuation_page_ids): if individuation_page_list:
mw.individuation_page_PTD = True mw.individuation_page_list = list(set(individuation_page_list))
# ============================================================================================= # =============================================================================================
is_inspect = fields.Boolean('需送检', compute='_compute_is_inspect', store=True, default=False) is_inspect = fields.Boolean('需送检', compute='_compute_is_inspect', store=True, default=False)
@@ -1836,7 +1863,7 @@ class CNCprocessing(models.Model):
# 将FTP的多面的程序单文件下载到临时目录 # 将FTP的多面的程序单文件下载到临时目录
def download_file_tmp(self, production_no, processing_panel): def download_file_tmp(self, production_no, processing_panel):
remotepath = os.path.join('/home/ftp/ftp_root/NC', production_no, 'return', processing_panel) remotepath = os.path.join('/', production_no, 'return', processing_panel)
serverdir = os.path.join('/tmp', production_no, 'return', processing_panel) serverdir = os.path.join('/tmp', production_no, 'return', processing_panel)
ftp_resconfig = self.env['res.config.settings'].get_values() ftp_resconfig = self.env['res.config.settings'].get_values()
ftp = FtpController(str(ftp_resconfig['ftp_host']), int(ftp_resconfig['ftp_port']), ftp_resconfig['ftp_user'], ftp = FtpController(str(ftp_resconfig['ftp_host']), int(ftp_resconfig['ftp_port']), ftp_resconfig['ftp_user'],
@@ -1927,7 +1954,8 @@ class SfWorkOrderBarcodes(models.Model):
self.write(val) self.write(val)
workorder_rfid = self.env['mrp.workorder'].search( workorder_rfid = self.env['mrp.workorder'].search(
[('production_id', '=', workorder.production_id.id), [('production_id', '=', workorder.production_id.id),
('processing_panel', '=', workorder.processing_panel)]) ('processing_panel', '=', workorder.processing_panel),
('state', '!=', 'rework')])
if workorder_rfid: if workorder_rfid:
for item in workorder_rfid: for item in workorder_rfid:
item.write({'rfid_code': barcode}) item.write({'rfid_code': barcode})
@@ -2009,7 +2037,7 @@ class WorkPieceDelivery(models.Model):
feeder_station_destination_id = fields.Many2one('sf.agv.site', '目的接驳站') feeder_station_destination_id = fields.Many2one('sf.agv.site', '目的接驳站')
task_delivery_time = fields.Datetime('任务下发时间') task_delivery_time = fields.Datetime('任务下发时间')
task_completion_time = fields.Datetime('任务完成时间') task_completion_time = fields.Datetime('任务完成时间')
def _get_agv_route_type_selection(self): def _get_agv_route_type_selection(self):
return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection'] return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection']
type = fields.Selection(selection=_get_agv_route_type_selection, string='类型') type = fields.Selection(selection=_get_agv_route_type_selection, string='类型')

View File

@@ -4,6 +4,7 @@ import requests
import base64 import base64
import hashlib import hashlib
import os import os
import re
from odoo import models, fields, api, _ from odoo import models, fields, api, _
from odoo.exceptions import ValidationError, UserError from odoo.exceptions import ValidationError, UserError
from odoo.modules import get_resource_path from odoo.modules import get_resource_path
@@ -29,6 +30,7 @@ class ResProductMo(models.Model):
model_width = fields.Float('模型宽(mm)', digits=(16, 3)) model_width = fields.Float('模型宽(mm)', digits=(16, 3))
model_height = fields.Float('模型高(mm)', digits=(16, 3)) model_height = fields.Float('模型高(mm)', digits=(16, 3))
model_volume = fields.Float('模型体积(m³)') model_volume = fields.Float('模型体积(m³)')
model_area = fields.Float('模型表面积(m²)')
model_machining_precision = fields.Selection(selection=_get_machining_precision, string='加工精度') model_machining_precision = fields.Selection(selection=_get_machining_precision, string='加工精度')
model_processing_panel = fields.Char('模型加工面板') model_processing_panel = fields.Char('模型加工面板')
model_remark = fields.Char('模型备注说明') model_remark = fields.Char('模型备注说明')
@@ -776,10 +778,40 @@ class ResProductMo(models.Model):
manual_quotation = fields.Boolean('人工编程', default=False, readonly=True) manual_quotation = fields.Boolean('人工编程', default=False, readonly=True)
machining_drawings = fields.Binary('2D加工图纸', readonly=True) machining_drawings = fields.Binary('2D加工图纸', readonly=True)
quality_standard = fields.Binary('质检标准', readonly=True) quality_standard = fields.Binary('质检标准', readonly=True)
part_name = fields.Char(string='零件名称', readonly=True) part_name = fields.Char(string='零件名称', compute='_compute_related_product', readonly=True, store=True)
part_number = fields.Char(string='零件图号', readonly=True) part_number = fields.Char(string='零件图号', compute='_compute_related_product', readonly=True, store=True)
machining_drawings_name = fields.Char(string='零件图号名称', readonly=True) machining_drawings_name = fields.Char(string='零件图号名称', readonly=True)
machining_drawings_mimetype = fields.Char(string='零件图号类型', readonly=True) machining_drawings_mimetype = fields.Char(string='零件图号类型', readonly=True)
model_url = fields.Char('模型文件地址')
glb_url = fields.Char('glb文件地址')
area = fields.Float('表面积(m²)')
auto_machining = fields.Boolean('自动化加工(模型识别)', default=False)
model_id = fields.Char('模型id')
@api.depends('name')
def _compute_related_product(self):
for record in self:
if record.categ_id.name == '坯料':
product_name = ''
match = re.search(r'(S\d{5}-\d)', record.name)
# 如果匹配成功,提取结果
if match:
product_name = match.group(0)
sale_order_name = ''
match_sale = re.search(r'S(\d+)', record.name)
if match_sale:
sale_order_name = match_sale.group(0)
sale_order = self.env['sale.order'].sudo().search(
[('name', '=', sale_order_name)])
if sale_order:
filtered_order_line = sale_order.order_line.filtered(
lambda order_line: re.search(f'{product_name}$', order_line.product_id.name)
)
record.part_number = filtered_order_line.product_id.part_number if filtered_order_line else None
record.part_name = filtered_order_line.product_id.part_name if filtered_order_line else None
@api.constrains('tool_length') @api.constrains('tool_length')
def _check_tool_length_size(self): def _check_tool_length_size(self):
if self.tool_length > 1000000: if self.tool_length > 1000000:
@@ -850,7 +882,7 @@ class ResProductMo(models.Model):
copy_product_id = product_id.with_user(self.env.ref("base.user_admin")).copy() copy_product_id = product_id.with_user(self.env.ref("base.user_admin")).copy()
copy_product_id.product_tmpl_id.active = True copy_product_id.product_tmpl_id.active = True
model_type = self.env['sf.model.type'].search([], limit=1) model_type = self.env['sf.model.type'].search([], limit=1)
attachment = self.attachment_create(item['model_name'], item['model_data']) # attachment = self.attachment_create(item['model_name'], item['model_data'])
# 获取坯料冗余配置 # 获取坯料冗余配置
if not item.get('embryo_redundancy'): if not item.get('embryo_redundancy'):
embryo_redundancy_id = model_type.embryo_tolerance_id embryo_redundancy_id = model_type.embryo_tolerance_id
@@ -873,10 +905,14 @@ class ResProductMo(models.Model):
'length': item['model_long'], 'length': item['model_long'],
'width': item['model_width'], 'width': item['model_width'],
'height': item['model_height'], 'height': item['model_height'],
'volume': item['model_long'] * item['model_width'] * item['model_height'], 'volume': item['model_volume'],
'model_file': '' if not item['model_file'] else base64.b64decode(item['model_file']), 'area': item['model_area'],
'model_name': attachment.name if attachment else None, # 'model_file': '' if not item['model_file'] else base64.b64decode(item['model_file']),
'upload_model_file': [(6, 0, [attachment.id])] if attachment else None, 'model_url': item['model_url'],
'glb_url': item['glb_url'],
'model_name': item['model_name'],
'auto_machining': item['auto_machining'],
# 'upload_model_file': [(6, 0, [attachment.id])] if attachment else None,
'list_price': item['price'], 'list_price': item['price'],
'materials_id': self.env['sf.production.materials'].search( 'materials_id': self.env['sf.production.materials'].search(
[('materials_no', '=', item['texture_code'])]).id, [('materials_no', '=', item['texture_code'])]).id,
@@ -896,6 +932,7 @@ class ResProductMo(models.Model):
'part_name': item.get('part_name') or '', 'part_name': item.get('part_name') or '',
'machining_drawings_name': item.get('machining_drawings_name') or '', 'machining_drawings_name': item.get('machining_drawings_name') or '',
'machining_drawings_mimetype': item.get('machining_drawings_mimetype') or '', 'machining_drawings_mimetype': item.get('machining_drawings_mimetype') or '',
'model_id': item['model_id'],
} }
tax_id = self.env['account.tax'].sudo().search( tax_id = self.env['account.tax'].sudo().search(
[('type_tax_use', '=', 'sale'), ('amount', '=', item.get('tax')), ('price_include', '=', 'True')]) [('type_tax_use', '=', 'sale'), ('amount', '=', item.get('tax')), ('price_include', '=', 'True')])
@@ -978,15 +1015,14 @@ class ResProductMo(models.Model):
vals = { vals = {
'name': '%s-%s-%s [%s %s-%s * %s * %s]' % ('R', 'name': '%s-%s-%s [%s %s-%s * %s * %s]' % ('R',
order_id.name, i, materials_id.name, materials_type_id.name, order_id.name, i, materials_id.name, materials_type_id.name,
item['model_long'] + embryo_redundancy_id.long, self.format_float(item['model_long'] + embryo_redundancy_id.long),
item['model_width'] + embryo_redundancy_id.width, self.format_float(item['model_width'] + embryo_redundancy_id.width),
item['model_height'] + embryo_redundancy_id.height), self.format_float(item['model_height'] + embryo_redundancy_id.height)),
'length': item['model_long'] + embryo_redundancy_id.long, 'length': self.format_float(item['model_long'] + embryo_redundancy_id.long),
'width': item['model_width'] + embryo_redundancy_id.width, 'width': self.format_float(item['model_width'] + embryo_redundancy_id.width),
'height': item['model_height'] + embryo_redundancy_id.height, 'height': self.format_float(item['model_height'] + embryo_redundancy_id.height),
'volume': (item['model_long'] + embryo_redundancy_id.long) * ( 'volume': self.format_float(item['blank_volume']),
item['model_width'] + embryo_redundancy_id.width) * ( 'area': self.format_float(item['blank_area']),
item['model_height'] + embryo_redundancy_id.height),
'embryo_model_type_id': model_type.id, 'embryo_model_type_id': model_type.id,
'list_price': item['price'], 'list_price': item['price'],
'materials_id': materials_id.id, 'materials_id': materials_id.id,
@@ -1079,6 +1115,9 @@ class ResProductMo(models.Model):
base64_data = base64.b64encode(image_data) base64_data = base64.b64encode(image_data)
return base64_data return base64_data
# 增加产品表面积
class ResProductFixture(models.Model): class ResProductFixture(models.Model):
_inherit = 'product.template' _inherit = 'product.template'
@@ -1091,6 +1130,7 @@ class ResProductFixture(models.Model):
fixture_material_type = fields.Char(string="夹具物料类型", related='fixture_material_id.name') fixture_material_type = fields.Char(string="夹具物料类型", related='fixture_material_id.name')
multi_mounting_type_id = fields.Many2one('sf.multi_mounting.type', string="联装类型") multi_mounting_type_id = fields.Many2one('sf.multi_mounting.type', string="联装类型")
model_file = fields.Binary(string="3D模型图") model_file = fields.Binary(string="3D模型图")
glb_url = fields.Char(string="3D模型图")
# 夹具物料基本参数 # 夹具物料基本参数
diameter = fields.Float('直径(mm)', digits=(16, 2)) diameter = fields.Float('直径(mm)', digits=(16, 2))

View File

@@ -59,18 +59,15 @@ class PurchaseOrder(models.Model):
production_id = self.env['mrp.production'].search([('origin', 'in', origins)]) production_id = self.env['mrp.production'].search([('origin', 'in', origins)])
purchase.production_count = len(production_id) purchase.production_count = len(production_id)
# def button_confirm(self): def button_confirm(self):
# super().button_confirm() for record in self:
# workorders = self.env['mrp.workorder'].search([('purchase_id', '=', self.id), ('state', '!=', 'cancel')]) for line in record.order_line:
# for workorder in workorders: if line.product_qty <= 0:
# if workorder.routing_type == '表面工艺' and workorder.is_subcontract is True: raise UserError('请对【产品】中的【数量】进行输入')
# move_out = workorder.move_subcontract_workorder_ids[1] if line.price_unit <= 0:
# for mo in move_out: raise UserError('请对【产品】中的【单价】进行输入')
# if mo.state != 'done': return super(PurchaseOrder, self).button_confirm()
# mo.write({'state': 'assigned', 'production_id': False})
# if not mo.move_line_ids:
# self.env['stock.move.line'].create(mo.get_move_line(workorder.production_id, workorder))
# return True
origin_sale_id = fields.Many2one('sale.order', string='销售订单号', store=True, compute='_compute_origin_sale_id') origin_sale_id = fields.Many2one('sale.order', string='销售订单号', store=True, compute='_compute_origin_sale_id')
origin_sale_ids = fields.Many2many('sale.order', string='销售订单号(多个)', store=True, origin_sale_ids = fields.Many2many('sale.order', string='销售订单号(多个)', store=True,
@@ -109,14 +106,18 @@ class PurchaseOrder(models.Model):
class PurchaseOrderLine(models.Model): class PurchaseOrderLine(models.Model):
_inherit = 'purchase.order.line' _inherit = 'purchase.order.line'
part_number = fields.Char('零件图号', store=True, compute='_compute_related_product') part_number = fields.Char('零件图号', store=True, compute='_compute_part_number')
part_name = fields.Char('零件名称', store=True, part_name = fields.Char('零件名称', store=True, compute='_compute_part_number')
compute='_compute_related_product')
related_product = fields.Many2one('product.product', string='关联产品', related_product = fields.Many2one('product.product', string='关联产品',
help='经此产品工艺加工成的成品') help='经此产品工艺加工成的成品')
manual_part_number = fields.Char()
manual_part_name = fields.Char()
@api.depends('product_id') @api.depends('product_id')
def _compute_related_product(self): def _compute_part_number(self):
for record in self: for record in self:
if record.part_number and record.part_name:
continue
if record.product_id.categ_id.name == '坯料': if record.product_id.categ_id.name == '坯料':
product_name = '' product_name = ''
match = re.search(r'(S\d{5}-\d)', record.product_id.name) match = re.search(r'(S\d{5}-\d)', record.product_id.name)
@@ -133,13 +134,13 @@ class PurchaseOrderLine(models.Model):
filtered_order_line = sale_order.order_line.filtered( filtered_order_line = sale_order.order_line.filtered(
lambda order_line: re.search(f'{product_name}$', order_line.product_id.name) lambda order_line: re.search(f'{product_name}$', order_line.product_id.name)
) )
record.part_number = filtered_order_line.product_id.part_number if filtered_order_line else None record.part_number = filtered_order_line.product_id.part_number
record.part_name = filtered_order_line.product_id.part_name if filtered_order_line else None record.part_name = filtered_order_line.product_id.part_name
else: else:
record.part_number = record.product_id.part_number record.part_number = record.product_id.part_number
record.part_name = record.product_id.part_name record.part_name = record.product_id.part_name
# if record.product_id.detailed_type: if record.manual_part_name:
# production_id = self.env['mrp.production'].search([('name', '=', record.order_id.origin)]) # 如果手动设置了 part_name使用手动设置的值
# record.related_product = production_id.product_id if production_id else False record.part_name = record.manual_part_name
# else: if record.manual_part_number:
# record.related_product = False record.part_number = record.manual_part_number

View File

@@ -59,7 +59,7 @@ class QuickEasyOrder(models.Model):
product_id = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_default').sudo().with_context(active_test=False).product_variant_id product_id = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_default').sudo().with_context(active_test=False).product_variant_id
# user_id = request.env.ref('base.user_admin').sudo() # user_id = request.env.ref('base.user_admin').sudo()
order_id = self.env['sale.order'].sale_order_create(company_id, 'XXXXX', 'XXXXX', 'XXXXX', order_id = self.env['sale.order'].sale_order_create(company_id, 'XXXXX', 'XXXXX', 'XXXXX',
str(datetime.now()), '现结', '支付宝', state='draft') str(datetime.now()), '现结', '支付宝', state='draft', model_display_version='v2')
order_id.default_code = obj.name order_id.default_code = obj.name
i = 1 i = 1
for item in res['bfm_process_order_list']: for item in res['bfm_process_order_list']:

View File

@@ -1,10 +1,13 @@
import logging import logging
import json import json
import re
from odoo import models, fields, api, _ from odoo import models, fields, api, _
from odoo.exceptions import UserError from odoo.exceptions import UserError
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
class SaleOrder(models.Model): class SaleOrder(models.Model):
_inherit = 'sale.order' _inherit = 'sale.order'
@@ -22,6 +25,9 @@ class SaleOrder(models.Model):
def confirm_to_supply_method(self): def confirm_to_supply_method(self):
self.state = 'supply method' self.state = 'supply method'
for line in self.order_line:
if line.product_id.auto_machining:
line.supply_method = 'automation'
def action_confirm(self): def action_confirm(self):
if self._get_forbidden_state_confirm() & set(self.mapped('state')): if self._get_forbidden_state_confirm() & set(self.mapped('state')):
@@ -39,13 +45,16 @@ class SaleOrder(models.Model):
product_template_id = self.env.ref('sf_dlm.product_template_sf').sudo().product_tmpl_id product_template_id = self.env.ref('sf_dlm.product_template_sf').sudo().product_tmpl_id
elif line.supply_method == 'outsourcing': elif line.supply_method == 'outsourcing':
bom_type = 'subcontract' bom_type = 'subcontract'
product_template_id = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_outsourcing').sudo() product_template_id = self.env.ref(
'jikimo_sale_multiple_supply_methods.product_template_outsourcing').sudo()
elif line.supply_method == 'purchase': elif line.supply_method == 'purchase':
product_template_id = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_purchase').sudo() product_template_id = self.env.ref(
'jikimo_sale_multiple_supply_methods.product_template_purchase').sudo()
elif line.supply_method == 'manual': elif line.supply_method == 'manual':
bom_type = 'normal' bom_type = 'normal'
product_template_id = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_manual_processing').sudo() product_template_id = self.env.ref(
'jikimo_sale_multiple_supply_methods.product_template_manual_processing').sudo()
# 复制成品模板上的属性 # 复制成品模板上的属性
line.product_id.product_tmpl_id.copy_template(product_template_id) line.product_id.product_tmpl_id.copy_template(product_template_id)
# 将模板上的single_manufacturing属性复制到成品上 # 将模板上的single_manufacturing属性复制到成品上
@@ -53,30 +62,39 @@ class SaleOrder(models.Model):
order_id = self order_id = self
product = line.product_id product = line.product_id
# 拼接方法需要的item结构 # 拼接方法需要的item结构,成品的模型数据信息就是坯料的数据信息
item = { item = {
'texture_code': product.materials_id.materials_no, 'texture_code': product.materials_id.materials_no,
'texture_type_code': product.materials_type_id.materials_no, 'texture_type_code': product.materials_type_id.materials_no,
'model_long': product.length, 'model_long': product.length,
'model_width': product.width, 'model_width': product.width,
'model_height': product.height, 'model_height': product.height,
'blank_volume': product.model_volume,
'blank_area': product.model_area,
'price': product.list_price, 'price': product.list_price,
'embryo_redundancy_id': line.embryo_redundancy_id, 'embryo_redundancy_id': line.embryo_redundancy_id,
} }
product_name = ''
match = re.search(r'(S\d{5}-\d)', product.name)
# 如果匹配成功,提取结果
if match:
product_name = match.group(0)
# 获取成品名结尾-n的n # 获取成品名结尾-n的n
product_seria = int(product.name.split('-')[-1]) product_seria = int(product_name.split('-')[-1])
# 成品供货方式为采购则不生成bom # 成品供货方式为采购则不生成bom
if line.supply_method != 'purchase': if line.supply_method != 'purchase':
bom_data = self.env['mrp.bom'].with_user(self.env.ref("base.user_admin")).get_bom(product) bom_data = self.env['mrp.bom'].with_user(self.env.ref("base.user_admin")).get_bom(product)
_logger.info('bom_data:%s' % bom_data) _logger.info('bom_data:%s' % bom_data)
if bom_data: if bom_data:
bom = self.env['mrp.bom'].with_user(self.env.ref("base.user_admin")).bom_create(product, 'normal', False) bom = self.env['mrp.bom'].with_user(self.env.ref("base.user_admin")).bom_create(product, 'normal',
False)
bom.with_user(self.env.ref("base.user_admin")).bom_create_line_has(bom_data) bom.with_user(self.env.ref("base.user_admin")).bom_create_line_has(bom_data)
else: else:
# 当成品上带有客供料选项时,生成坯料时选择“客供料”路线 # 当成品上带有客供料选项时,生成坯料时选择“客供料”路线
if line.embryo_redundancy_id: if line.embryo_redundancy_id:
# 将成品模板的内容复制到成品上 # 将成品模板的内容复制到成品上
customer_provided_embryo = self.env.ref('jikimo_sale_multiple_supply_methods.product_template_embryo_customer_provided').sudo() customer_provided_embryo = self.env.ref(
'jikimo_sale_multiple_supply_methods.product_template_embryo_customer_provided').sudo()
# 创建坯料客供料的批量不需要创建bom # 创建坯料客供料的批量不需要创建bom
material_customer_provided_embryo = self.env['product.template'].sudo().no_bom_product_create( material_customer_provided_embryo = self.env['product.template'].sudo().no_bom_product_create(
customer_provided_embryo.with_context(active_test=False).product_variant_id, customer_provided_embryo.with_context(active_test=False).product_variant_id,
@@ -86,7 +104,8 @@ class SaleOrder(models.Model):
product_bom_material_customer_provided = self.env['mrp.bom'].with_user( product_bom_material_customer_provided = self.env['mrp.bom'].with_user(
self.env.ref("base.user_admin")).bom_create( self.env.ref("base.user_admin")).bom_create(
product, bom_type, 'product') product, bom_type, 'product')
product_bom_material_customer_provided.with_user(self.env.ref("base.user_admin")).bom_create_line_has( product_bom_material_customer_provided.with_user(
self.env.ref("base.user_admin")).bom_create_line_has(
material_customer_provided_embryo) material_customer_provided_embryo)
elif line.product_id.materials_type_id.gain_way == '自加工': elif line.product_id.materials_type_id.gain_way == '自加工':
self_machining_id = self.env.ref('sf_dlm.product_embryo_sf_self_machining').sudo() self_machining_id = self.env.ref('sf_dlm.product_embryo_sf_self_machining').sudo()
@@ -115,10 +134,11 @@ class SaleOrder(models.Model):
outsource_id = self.env.ref('sf_dlm.product_embryo_sf_outsource').sudo() outsource_id = self.env.ref('sf_dlm.product_embryo_sf_outsource').sudo()
# 创建坯料 # 创建坯料
outsource_embryo = self.env['product.template'].sudo().no_bom_product_create(outsource_id, outsource_embryo = self.env['product.template'].sudo().no_bom_product_create(outsource_id,
item, item,
order_id, order_id,
'subcontract', 'subcontract',
product_seria, product) product_seria,
product)
if outsource_embryo == -3: if outsource_embryo == -3:
raise UserError('该订单模型的材料型号暂未设置获取方式和供应商,请先配置再进行分配') raise UserError('该订单模型的材料型号暂未设置获取方式和供应商,请先配置再进行分配')
# 创建坯料的bom # 创建坯料的bom
@@ -138,11 +158,12 @@ class SaleOrder(models.Model):
elif line.product_id.materials_type_id.gain_way == '采购': elif line.product_id.materials_type_id.gain_way == '采购':
purchase_id = self.env.ref('sf_dlm.product_embryo_sf_purchase').sudo() purchase_id = self.env.ref('sf_dlm.product_embryo_sf_purchase').sudo()
purchase_embryo = self.env['product.template'].sudo().no_bom_product_create(purchase_id, purchase_embryo = self.env['product.template'].sudo().no_bom_product_create(purchase_id,
item, item,
order_id, order_id,
'purchase', product_seria, 'purchase',
product) product_seria,
if purchase_embryo == -3: product)
if purchase_embryo and purchase_embryo == -3:
raise UserError('该订单模型的材料型号暂未设置获取方式和供应商,请先配置再进行分配') raise UserError('该订单模型的材料型号暂未设置获取方式和供应商,请先配置再进行分配')
else: else:
# 产品配置bom # 产品配置bom
@@ -151,15 +172,15 @@ class SaleOrder(models.Model):
product_bom_purchase.with_user(self.env.ref("base.user_admin")).bom_create_line_has( product_bom_purchase.with_user(self.env.ref("base.user_admin")).bom_create_line_has(
purchase_embryo) purchase_embryo)
return super(SaleOrder, self).action_confirm() return super(SaleOrder, self).action_confirm()
def action_show_cancel_wizard(self): def action_show_cancel_wizard(self):
wizard = self.env['sf.sale.order.cancel.wizard'].create({ wizard = self.env['sf.sale.order.cancel.wizard'].create({
'order_id': self.id, 'order_id': self.id,
}) })
# 创建关联单据行 # 创建关联单据行
self.env['sf.sale.order.cancel.line'].create_from_order(wizard.id, self) self.env['sf.sale.order.cancel.line'].create_from_order(wizard.id, self)
return { return {
'name': '取消销售订单', 'name': '取消销售订单',
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
@@ -169,6 +190,7 @@ class SaleOrder(models.Model):
'res_id': wizard.id, 'res_id': wizard.id,
} }
class SaleOrderLine(models.Model): class SaleOrderLine(models.Model):
_inherit = 'sale.order.line' _inherit = 'sale.order.line'
part_number = fields.Char('零件图号', related='product_id.part_number', readonly=True) part_number = fields.Char('零件图号', related='product_id.part_number', readonly=True)
@@ -188,3 +210,14 @@ class SaleOrderLine(models.Model):
if vals['supply_method'] == 'purchase' and line.is_incoming_material: if vals['supply_method'] == 'purchase' and line.is_incoming_material:
raise UserError('当前(%s)产品为客供料,不能选择外购' % ','.join(line.mapped('product_id.name'))) raise UserError('当前(%s)产品为客供料,不能选择外购' % ','.join(line.mapped('product_id.name')))
return super(SaleOrderLine, self).write(vals) return super(SaleOrderLine, self).write(vals)
cancel_auto_machining = fields.Boolean('是否取消自动化加工', compute='_compute_cancel_auto_machining', store=True)
cancel_auto_machining_reason = fields.Char('更改供货原因')
@api.depends('product_id', 'supply_method')
def _compute_cancel_auto_machining(self):
for line in self:
line.cancel_auto_machining = True if line.product_id.auto_machining \
and line.supply_method != 'automation' else False

View File

@@ -2,6 +2,7 @@
import base64 import base64
import random import random
import re import re
import traceback
import qrcode import qrcode
from itertools import groupby from itertools import groupby
@@ -292,7 +293,8 @@ class StockRule(models.Model):
if production_item.product_id.id in product_id_to_production_names: if production_item.product_id.id in product_id_to_production_names:
# 同一个产品多个制造订单对应一个编程单和模型库 # 同一个产品多个制造订单对应一个编程单和模型库
# 只调用一次fetchCNC并将所有生产订单的名称作为字符串传递 # 只调用一次fetchCNC并将所有生产订单的名称作为字符串传递
if not production_item.programming_no and production_item.production_type in ['自动化产线加工', '人工线下加工']: if not production_item.programming_no and production_item.production_type in ['自动化产线加工',
'人工线下加工']:
if not production_programming.programming_no: if not production_programming.programming_no:
production_item.fetchCNC( production_item.fetchCNC(
', '.join(product_id_to_production_names[production_item.product_id.id])) ', '.join(product_id_to_production_names[production_item.product_id.id]))
@@ -353,9 +355,9 @@ class StockRule(models.Model):
) )
for p in production_process: for p in production_process:
logging.info('production_process:%s' % p.name) logging.info('production_process:%s' % p.name)
process_parameter = production_item.product_id.model_process_parameters_ids.filtered( process_parameters = production_item.product_id.model_process_parameters_ids.filtered(
lambda pm: pm.process_id.id == p.id) lambda pm: pm.process_id.id == p.id)
if process_parameter: for process_parameter in process_parameters:
i += 1 i += 1
route_production_process = self.env[ route_production_process = self.env[
'mrp.routing.workcenter'].search( 'mrp.routing.workcenter'].search(
@@ -560,6 +562,18 @@ class StockPicking(models.Model):
sale_order_id = fields.Many2one('sale.order', '销售单号', compute='_compute_move_ids', store=True) sale_order_id = fields.Many2one('sale.order', '销售单号', compute='_compute_move_ids', store=True)
picking_type_sequence_code = fields.Char(related='picking_type_id.sequence_code') picking_type_sequence_code = fields.Char(related='picking_type_id.sequence_code')
part_numbers = fields.Char(string="零件图号", compute='_compute_part_info', store=True, index=True)
part_names = fields.Char(string="零件名称", compute='_compute_part_info', store=True, index=True)
@api.depends('move_ids_without_package.part_number', 'move_ids_without_package.part_name')
def _compute_part_info(self):
for picking in self:
# 聚合所有关联行的 part_number 和 part_name
part_numbers = picking.move_ids_without_package.mapped('part_number')
part_names = picking.move_ids_without_package.mapped('part_name')
picking.part_numbers = ','.join(filter(None, part_numbers))
picking.part_names = ','.join(filter(None, part_names))
@api.depends('move_ids', 'move_ids.product_id') @api.depends('move_ids', 'move_ids.product_id')
def _compute_move_ids(self): def _compute_move_ids(self):
for item in self: for item in self:
@@ -587,7 +601,7 @@ class StockPicking(models.Model):
item.address_of_delivery = sale_info.address_of_delivery item.address_of_delivery = sale_info.address_of_delivery
# 设置外协出入单的名称 # 设置外协出入单的名称
def _get_name_Res(self, rescode,sequence): def _get_name_Res(self, rescode, sequence):
last_picking = self.sudo().search([('name', 'ilike', rescode)], order='name desc', limit=1) last_picking = self.sudo().search([('name', 'ilike', rescode)], order='name desc', limit=1)
sequence_id = sequence.next_by_id() sequence_id = sequence.next_by_id()
name_without_prefix = last_picking.name.removeprefix(rescode) name_without_prefix = last_picking.name.removeprefix(rescode)
@@ -624,7 +638,8 @@ class StockPicking(models.Model):
if move_in: if move_in:
workorder = move_in.subcontract_workorder_id workorder = move_in.subcontract_workorder_id
workorders = workorder.production_id.workorder_ids workorders = workorder.production_id.workorder_ids
subcontract_workorders = workorders.filtered(lambda wo: wo.is_subcontract == True and wo.state!='cancel').sorted('sequence') subcontract_workorders = workorders.filtered(
lambda wo: wo.is_subcontract == True and wo.state != 'cancel').sorted('sequence')
# if workorder == subcontract_workorders[-1]: # if workorder == subcontract_workorders[-1]:
# self.env['stock.quant']._update_reserved_quantity( # self.env['stock.quant']._update_reserved_quantity(
# move_in.product_id, move_in.location_dest_id, move_in.product_uom_qty, # move_in.product_id, move_in.location_dest_id, move_in.product_uom_qty,
@@ -673,7 +688,8 @@ class StockPicking(models.Model):
# 如果当前工单是是制造订单的最后一个工艺外协工单 # 如果当前工单是是制造订单的最后一个工艺外协工单
if workorder == next((workorder for workorder in reversed(sorted_workorders) if workorder.is_subcontract), if workorder == next((workorder for workorder in reversed(sorted_workorders) if workorder.is_subcontract),
None): None):
move_dest_id = item.move_raw_ids[0].id if item.move_raw_ids:
move_dest_id = item.move_raw_ids[0].id
else: else:
# 从sorted_workorders中找到上一工单的move # 从sorted_workorders中找到上一工单的move
if len(sorted_workorders) > 1: if len(sorted_workorders) > 1:
@@ -709,6 +725,7 @@ class StockPicking(models.Model):
moves_out._action_confirm() moves_out._action_confirm()
moves_out._assign_picking_post_process(new=new_picking) moves_out._assign_picking_post_process(new=new_picking)
@api.depends('move_type', 'immediate_transfer', 'move_ids.state', 'move_ids.picking_id') @api.depends('move_type', 'immediate_transfer', 'move_ids.state', 'move_ids.picking_id')
def _compute_state(self): def _compute_state(self):
super(StockPicking, self)._compute_state() super(StockPicking, self)._compute_state()
@@ -750,32 +767,69 @@ class ReStockMove(models.Model):
@api.depends('product_id') @api.depends('product_id')
def _compute_part_info(self): def _compute_part_info(self):
for move in self: try:
if move.product_id.categ_id.type == '成品': for move in self:
move.part_number = move.product_id.part_number if move.product_id.categ_id.type == '成品':
move.part_name = move.product_id.part_name move.part_number = move.product_id.part_number
elif move.product_id.categ_id.type == '坯料': move.part_name = move.product_id.part_name
product_name = '' elif move.product_id.categ_id.type == '坯料':
match = re.search(r'(S\d{5}-\d)', move.product_id.name) product_name = ''
# 如果匹配成功,提取结果 match = re.search(r'(S\d{5}-\d)', move.product_id.name)
if match: # 如果匹配成功,提取结果
product_name = match.group(0)
if move.picking_id.sale_order_id:
sale_order = move.picking_id.sale_order_id
else:
sale_order_name = ''
match = re.search(r'(S\d+)', move.product_id.name)
if match: if match:
sale_order_name = match.group(0) product_name = match.group(0)
sale_order = self.env['sale.order'].sudo().search( if move.picking_id.sale_order_id:
[('name', '=', sale_order_name)]) sale_order = move.picking_id.sale_order_id
filtered_order_line = sale_order.order_line.filtered( else:
lambda production: re.search(f'{product_name}$', production.product_id.name) sale_order_name = ''
) match = re.search(r'(S\d+)', move.product_id.name)
if match:
sale_order_name = match.group(0)
sale_order = self.env['sale.order'].sudo().search(
[('name', '=', sale_order_name)])
filtered_order_line = sale_order.order_line.filtered(
lambda production: re.search(f'{product_name}$', production.product_id.name)
)
if filtered_order_line:
move.part_number = filtered_order_line.part_number
move.part_name = filtered_order_line.part_name
elif move.product_id.categ_id.type == '原材料':
production_id = move.production_id or move.raw_material_production_id
if not production_id:
if not move.origin:
continue
logging.info('制造订单的调拨单 %s', move.origin)
production_id = self.env['mrp.production'].sudo().search(
[('name', '=', move.origin.split(',')[0] if move.origin else '')], limit=1)
if not production_id:
continue
product_name = ''
logging.info('制造订单的产品 %s', production_id.product_id.name)
match = re.search(r'(S\d{5}-\d)', production_id.product_id.name)
# 如果匹配成功,提取结果
if match:
product_name = match.group(0)
if move.picking_id.sale_order_id:
sale_order = move.picking_id.sale_order_id
else:
sale_order_name = ''
match = re.search(r'(S\d+)', production_id.product_id.name)
if match:
sale_order_name = match.group(0)
sale_order = self.env['sale.order'].sudo().search(
[('name', '=', sale_order_name)])
filtered_order_line = sale_order.order_line.filtered(
lambda production: re.search(f'{product_name}$', production.product_id.name)
)
if filtered_order_line:
move.part_number = filtered_order_line.part_number
move.part_name = filtered_order_line.part_name
except Exception as e:
traceback_error = traceback.format_exc()
logging.error("零件图号 零件名称获取失败:%s" % traceback_error)
if filtered_order_line:
move.part_number = filtered_order_line.part_number
move.part_name = filtered_order_line.part_name
def _get_stock_move_values_Res(self, item, picking_type_id, group_id, move_dest_ids=False): def _get_stock_move_values_Res(self, item, picking_type_id, group_id, move_dest_ids=False):
route_id = self.env.ref('sf_manufacturing.route_surface_technology_outsourcing').id route_id = self.env.ref('sf_manufacturing.route_surface_technology_outsourcing').id
stock_rule = self.env['stock.rule'].sudo().search( stock_rule = self.env['stock.rule'].sudo().search(
@@ -795,6 +849,7 @@ class ReStockMove(models.Model):
# 'route_ids': False if not route else [(4, route.id)], # 'route_ids': False if not route else [(4, route.id)],
'date_deadline': datetime.now(), 'date_deadline': datetime.now(),
'picking_type_id': picking_type_id, 'picking_type_id': picking_type_id,
# 'is_subcontract': True,
} }
return move_values return move_values
@@ -808,7 +863,7 @@ class ReStockMove(models.Model):
picking_type_id = self.env.ref('sf_manufacturing.outcontract_picking_in').id picking_type_id = self.env.ref('sf_manufacturing.outcontract_picking_in').id
sequence = self.env.ref('sf_manufacturing.sequence_stock_picking_in') sequence = self.env.ref('sf_manufacturing.sequence_stock_picking_in')
return { return {
'name': self.env['stock.picking']._get_name_Res(rescode,sequence), 'name': self.env['stock.picking']._get_name_Res(rescode, sequence),
'origin': item.name, 'origin': item.name,
'surface_technics_parameters_id': sorted_workorders.surface_technics_parameters_id.id, 'surface_technics_parameters_id': sorted_workorders.surface_technics_parameters_id.id,
'company_id': self.mapped('company_id').id, 'company_id': self.mapped('company_id').id,
@@ -1032,6 +1087,9 @@ class ReStockMove(models.Model):
productions = self.env['mrp.production'].search( productions = self.env['mrp.production'].search(
[('origin', '=', production.origin), ('product_id', '=', production.product_id.id)]) [('origin', '=', production.origin), ('product_id', '=', production.product_id.id)])
res['origin'] = ','.join(productions.mapped('name')) res['origin'] = ','.join(productions.mapped('name'))
if self.picking_type_id.name == '客供料入库':
self.picking_id.sudo().write(
{'origin': res['origin'] if res.get('origin') else self[0].picking_id.origin})
return res return res
def _get_new_picking_values(self): def _get_new_picking_values(self):
@@ -1061,6 +1119,13 @@ class ReStockMove(models.Model):
if self.state != 'assigned': if self.state != 'assigned':
self.state = 'assigned' self.state = 'assigned'
return self.action_show_details() return self.action_show_details()
def _prepare_move_line_vals(self, quantity=None, reserved_quant=None):
res = super(ReStockMove, self)._prepare_move_line_vals(quantity, reserved_quant)
if self.subcontract_workorder_id:
if self.subcontract_workorder_id.production_id.move_raw_ids.move_line_ids:
res['lot_id'] = self.subcontract_workorder_id.production_id.move_raw_ids.move_line_ids[0].lot_id.id
return res
class ReStockQuant(models.Model): class ReStockQuant(models.Model):

View File

@@ -15,6 +15,23 @@
</field> </field>
</record> </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"> <record id="action_agv_site_form" model="ir.actions.act_window">
<field name="name">AGV站点</field> <field name="name">AGV站点</field>
<field name="res_model">sf.agv.site</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="route_type" string="类型" required="1" attrs="{'readonly': [('id', '!=', False)]}"/>
<field name="start_site_id" required="1" options="{'no_create': True}" string="起点接驳站" <field name="start_site_id" required="1" options="{'no_create': True}" string="起点接驳站"
attrs="{'readonly': [('id', '!=', False)]}"/> 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}"--> <!-- <field name="destination_production_line_id" required="1" options="{'no_create': True}"-->
<!-- attrs="{'readonly': [('id', '!=', False)]}"/>--> <!-- attrs="{'readonly': [('id', '!=', False)]}"/>-->
<field name="workcenter_id"/> <field name="workcenter_id"/>

View File

@@ -450,7 +450,9 @@
</button> </button>
</div> </div>
<field name="product_id" position="after"> <field name="product_id" position="after">
<field name="model_file" string="产品模型" readonly="1" widget="Viewer3D"/> <field name="model_file" string="产品模型" readonly="1" widget="Viewer3D" attrs="{'invisible': [('model_file', '=', False)]}"/>
<field name="glb_url" widget="Viewer3D" string="模型" readonly="1" force_save="1"
attrs="{'invisible': [('glb_url', '=', False)]}"/>
</field> </field>
</field> </field>
</record> </record>

View File

@@ -227,8 +227,7 @@
<!-- attrs='{"invisible": [("rework_flag","=",True)]}' confirm="是否返工"/>--> <!-- attrs='{"invisible": [("rework_flag","=",True)]}' confirm="是否返工"/>-->
</xpath> </xpath>
<xpath expr="//page[1]" position="before"> <xpath expr="//page[1]" position="before">
<page string="开料要求" <page string="开料要求" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "CMR")]}'>
attrs='{"invisible": [("routing_type","not in",("切割", "线切割", "人工线下加工"))]}'>
<group> <group>
<group> <group>
<field name="product_tmpl_id_materials_id" widget="many2one"/> <field name="product_tmpl_id_materials_id" widget="many2one"/>
@@ -252,7 +251,8 @@
<field name="date_planned_finished" invisible="1"/> <field name="date_planned_finished" invisible="1"/>
<field name="duration" widget="mrp_timer" <field name="duration" widget="mrp_timer"
invisible="1" sum="real duration"/> invisible="1" sum="real duration"/>
<field name="glb_file" readonly="1" widget="Viewer3D" string="加工模型"/> <field name="glb_file" readonly="1" widget="Viewer3D" string="加工模型" attrs="{'invisible': [('glb_file', '=', False)]}"/>
<field name="glb_url" readonly="1" widget="Viewer3D" string="加工模型" attrs="{'invisible': [('glb_url', '=', False)]}"/>
<field name="manual_quotation" readonly="1" <field name="manual_quotation" readonly="1"
attrs="{'invisible': [('routing_type', 'not in', ['CNC加工', '人工线下加工'])]}"/> attrs="{'invisible': [('routing_type', 'not in', ['CNC加工', '人工线下加工'])]}"/>
<field name="processing_panel" readonly="1" <field name="processing_panel" readonly="1"
@@ -325,7 +325,7 @@
<xpath expr="//page[1]" position="before"> <xpath expr="//page[1]" position="before">
<page string="工件装夹" attrs='{"invisible": [("routing_type","!=","装夹预调")]}'> <page string="工件装夹" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "WCP")]}'>
<group> <group>
<!-- <field name="_barcode_scanned" widget="barcode_handler"/> --> <!-- <field name="_barcode_scanned" widget="barcode_handler"/> -->
<group string="托盘"> <group string="托盘">
@@ -347,7 +347,7 @@
placeholder="如有预调程序信息请在此处输入....."/> placeholder="如有预调程序信息请在此处输入....."/>
</group> </group>
</page> </page>
<page string="前置三元检测定位参数" attrs='{"invisible": [("routing_type","!=","装夹预调")]}'> <page string="前置三元检测定位参数" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "ITD_PP")]}'>
<div>左面:</div> <div>左面:</div>
<div class="o_address_format"> <div class="o_address_format">
@@ -504,17 +504,7 @@
</group> </group>
</page> </page>
<page string="2D加工图纸" <page string="工件配送" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "WD")]}'>
attrs='{"invisible": [("routing_type","not in",["装夹预调", "人工线下加工"])]}'>
<field name="machining_drawings" widget="adaptive_viewer"/>
</page>
<page string="质检标准" attrs="{'invisible': [('routing_type','!=','装夹预调')]}">
<field name="quality_standard" widget="adaptive_viewer"/>
</page>
<page string="工件配送"
attrs="{'invisible': [('routing_type','!=','装夹预调')]}">
<field name="workpiece_delivery_ids"> <field name="workpiece_delivery_ids">
<tree editable="bottom"> <tree editable="bottom">
<field name="production_id" invisible="1"/> <field name="production_id" invisible="1"/>
@@ -542,11 +532,19 @@
attrs='{"invisible": ["|", ("state","!=","progress"), ("routing_type","!=","装夹预调")]}'/> attrs='{"invisible": ["|", ("state","!=","progress"), ("routing_type","!=","装夹预调")]}'/>
</xpath> </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"> <xpath expr="//page[1]" position="before">
<field name="results" invisible="1"/> <field name="results" invisible="1"/>
<field name="individuation_page_PTD" invisible="1"/> <field name="individuation_page_list" invisible="1"/>
<page string="后置三元检测" attrs='{"invisible": [("individuation_page_PTD", "=", False)]}'> <page string="后置三元检测" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "PTD")]}'>
<group> <group>
<field name="test_results" <field name="test_results"
attrs='{"readonly":["&amp;","|",("state","!=","to be detected"), "|",("routing_type","=","CNC加工"),("is_inspect", "=", True),("state","in",["done","rework"])], attrs='{"readonly":["&amp;","|",("state","!=","to be detected"), "|",("routing_type","=","CNC加工"),("is_inspect", "=", True),("state","in",["done","rework"])],
@@ -568,16 +566,16 @@
<!-- attrs='{"invisible": ["|","|",("state","!=","progress"),("user_permissions","=",False),("results","=","合格")]}'/>--> <!-- attrs='{"invisible": ["|","|",("state","!=","progress"),("user_permissions","=",False),("results","=","合格")]}'/>-->
<!-- </div>--> <!-- </div>-->
</page> </page>
<page string="2D加工图纸" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'> <page string="2D加工图纸" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "2D_MD")]}'>
<field name="machining_drawings" widget="adaptive_viewer"/> <field name="machining_drawings" widget="adaptive_viewer"/>
</page> </page>
<page string="质检标准" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'> <page string="质检标准" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "QIS")]}'>
<field name="quality_standard" widget="adaptive_viewer"/> <field name="quality_standard" widget="adaptive_viewer"/>
</page> </page>
</xpath> </xpath>
<xpath expr="//page[1]" position="before"> <xpath expr="//page[1]" position="before">
<page string="CNC程序" attrs='{"invisible": [("routing_type","not in",["CNC加工", "人工线下加工"])]}'> <page string="CNC程序" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "CNC_P")]}'>
<field name="cnc_ids" widget="one2many" string="工作程序" default_order="sequence_number,id" <field name="cnc_ids" widget="one2many" string="工作程序" default_order="sequence_number,id"
readonly="0"> readonly="0">
<tree> <tree>
@@ -601,7 +599,7 @@
</field> </field>
</page> </page>
<page string="CMM程序" attrs='{"invisible": [("routing_type","not in",["CNC加工", "人工线下加工"])]}'> <page string="CMM程序" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "CMM_P")]}'>
<field name="cmm_ids" widget="one2many" string="CMM程序" readonly="1"> <field name="cmm_ids" widget="one2many" string="CMM程序" readonly="1">
<tree> <tree>
<field name="sequence_number"/> <field name="sequence_number"/>
@@ -614,7 +612,7 @@
</page> </page>
</xpath> </xpath>
<xpath expr="//page[1]" position="before"> <xpath expr="//page[1]" position="before">
<page string="解除装夹" attrs='{"invisible": [("routing_type","!=","解除装夹")]}'> <page string="解除装夹" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "DCP")]}'>
<!-- <field name="tray_id" readonly="1"/>--> <!-- <field name="tray_id" readonly="1"/>-->
<!-- <div class="col-12 col-lg-6 o_setting_box">--> <!-- <div class="col-12 col-lg-6 o_setting_box">-->
<!-- <button type="object" class="oe_highlight" name="unbindtray" string="解除装夹"--> <!-- <button type="object" class="oe_highlight" name="unbindtray" string="解除装夹"-->

View File

@@ -18,9 +18,13 @@
<xpath expr="//page/field[@name='order_line']/tree/field[@name='remark']" position="before"> <xpath expr="//page/field[@name='order_line']/tree/field[@name='remark']" position="before">
<field name="supply_method" attrs="{'invisible': [('state', '=', 'draft')], 'required': [('state', '=', 'supply method')]}" /> <field name="supply_method" attrs="{'invisible': [('state', '=', 'draft')], 'required': [('state', '=', 'supply method')]}" />
</xpath> </xpath>
<xpath expr="//field[@name='order_line']/tree/field[@name='model_glb_file']" position="before"> <xpath expr="//field[@name='order_line']/tree/field[@name='product_template_id']" position="after">
<field name="part_number" optional="show" class="section_and_note_text"/> <field name="part_number" optional="show" class="section_and_note_text"/>
</xpath> </xpath>
<!-- <xpath expr="//field[@name='order_line']/tree/field[@name='remark']" position="before"> -->
<!-- <field name="cancel_auto_machining" invisible="1"/> -->
<!-- <field name="cancel_auto_machining_reason" optional="show" attrs="{'required': [('cancel_auto_machining', '=', True),('state', 'not in', ['draft', 'sent'])]}"/> -->
<!-- </xpath> -->
<!-- <xpath expr="//header/button[@name='action_cancel']" position="attributes"> --> <!-- <xpath expr="//header/button[@name='action_cancel']" position="attributes"> -->
<!-- <attribute name="attrs">{'invisible': [('state', '!=', 'draft')]}</attribute> --> <!-- <attribute name="attrs">{'invisible': [('state', '!=', 'draft')]}</attribute> -->
@@ -67,7 +71,7 @@
<record id="sale.action_quotations_with_onboarding" model="ir.actions.act_window"> <record id="sale.action_quotations_with_onboarding" model="ir.actions.act_window">
<field name="search_view_id" ref="jikimo_sale_order_view_search_inherit_quotation_supply_method"/> <field name="search_view_id" ref="jikimo_sale_order_view_search_inherit_quotation_supply_method"/>
<field name="context">{'search_default_draft': 1}</field> <field name="context">{'search_default_supply_method': 1}</field>
</record> </record>
<record id="action_quotations_supply_method" model="ir.actions.act_window"> <record id="action_quotations_supply_method" model="ir.actions.act_window">

View File

@@ -67,6 +67,10 @@
<filter string="追溯参考" name="retrospect" domain="[]" <filter string="追溯参考" name="retrospect" domain="[]"
context="{'group_by': 'retrospect_ref'}"/> context="{'group_by': 'retrospect_ref'}"/>
</xpath> </xpath>
<xpath expr="//field[@name='picking_type_id']" position="after">
<field name="part_numbers" string="零件图号" filter_domain="[('part_numbers', 'ilike', self)]"/>
<field name="part_names" string="零件名称" filter_domain="[('part_names', 'ilike', self)]"/>
</xpath>
</field> </field>
</record> </record>
@@ -87,7 +91,8 @@
<attribute name="invisible">True</attribute> <attribute name="invisible">True</attribute>
</xpath> </xpath>
<xpath expr="//form//button[@name='action_assign_serial_show_details']" position="after"> <xpath expr="//form//button[@name='action_assign_serial_show_details']" position="after">
<button name="button_update_the_sequence_number" type="object" class="btn-link" data-hotkey="k" title="Assign Serial Numbers"> <button name="button_update_the_sequence_number" type="object" class="btn-link" data-hotkey="k"
title="Assign Serial Numbers">
<span>更新序列号</span> <span>更新序列号</span>
</button> </button>
</xpath> </xpath>

View File

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

View File

@@ -140,7 +140,7 @@ class ReworkWizard(models.TransientModel):
and item.process_parameters_id == work.surface_technics_parameters_id) or and item.process_parameters_id == work.surface_technics_parameters_id) or
(item.route_id.name == work.name and item.panel (item.route_id.name == work.name and item.panel
and item.panel == work.processing_panel) or and item.panel == work.processing_panel) or
(item.route_id == work.routing_workcenter_id (item.route_id == work.routing_work_center_id
and not work.processing_panel and not work.processing_panel
and not work.surface_technics_parameters_id)) and not work.surface_technics_parameters_id))
if route: if route:

View File

@@ -72,9 +72,29 @@ class SFSaleOrderCancelWizard(models.TransientModel):
if purchase_orders: if purchase_orders:
purchase_orders.write({'state': 'cancel'}) purchase_orders.write({'state': 'cancel'})
# 取消销售订单关联的采购申请明细
purchase_request_lines = self.env['purchase.request.line'].search([
('origin', '=', self.order_id.name)
])
if purchase_request_lines:
purchase_request_lines.write({'request_state': 'cancel'})
# 取消销售订单关联的采购申请
purchase_requests = self.env['purchase.request'].search([
('origin', '=', self.order_id.name)
])
if purchase_requests:
purchase_requests.write({'state': 'cancel'})
# 取消销售订单 # 取消销售订单
result = self.order_id.action_cancel() result = self.order_id.action_cancel()
# 取消制造订单的排程单
mo_plan_orders = self.env['sf.production.plan'].search([
('origin', '=', self.order_id.name)])
if mo_plan_orders:
mo_plan_orders.write({'state': 'cancel'})
# 取消关联的制造订单及其采购单 # 取消关联的制造订单及其采购单
manufacturing_orders = self.env['mrp.production'].search([ manufacturing_orders = self.env['mrp.production'].search([
('origin', '=', self.order_id.name) ('origin', '=', self.order_id.name)
@@ -182,6 +202,22 @@ class SFSaleOrderCancelLine(models.TransientModel):
'progress': '加工中', 'progress': '加工中',
'assigned': '就绪' 'assigned': '就绪'
} }
plan_map_dict = {
'draft': '待排程',
'done': '已排程',
'processing': '加工中',
'finished': '已完成',
'cancel': '已取消'}
purchase_request_map_dict = {
'draft': '草稿',
'to_approve': '待批准',
'approved': '已批准',
'done': '已完成',
'cancel': '已取消',
'rejected': '已驳回',
'in_progress': '处理中'
}
module_name_dict = { module_name_dict = {
'purchase': '采购', 'purchase': '采购',
@@ -195,6 +231,7 @@ class SFSaleOrderCancelLine(models.TransientModel):
'point_of_sale': '销售', 'point_of_sale': '销售',
'website': '网站', 'website': '网站',
'sf_plan': '计划', 'sf_plan': '计划',
'purchase_request': '采购',
} }
# 检查销售订单 # 检查销售订单
@@ -288,6 +325,33 @@ class SFSaleOrderCancelLine(models.TransientModel):
} }
lines.append(self.create(vals)) lines.append(self.create(vals))
# 检查所有的排程单
sf_plan_orders = self.env['sf.production.plan'].search([
('origin', '=', order.name)])
if sf_plan_orders:
p1 = 0
for plan_order in sf_plan_orders:
if not plan_order.product_id.default_code:
product_name = plan_order.product_id.name
else:
product_name = f'[{plan_order.product_id.default_code}] {plan_order.product_id.name}'
p1 += 1
vals = {
'wizard_id': wizard_id,
'sequence': sequence,
'category': '排程',
'doc_name': '排程单',
'operation_type': '',
'doc_number': plan_order.name,
'line_number': p1,
'product_name': product_name,
'quantity': 1,
'doc_state': plan_map_dict.get(plan_order.state, plan_order.state),
'cancel_reason': '已有异动' if plan_order.state not in ['draft', 'cancel'] else ''
}
lines.append(self.create(vals))
sequence += 1
# 检查组件的制造单 # 检查组件的制造单
# component_mos = self.env['mrp.production'].search([ # component_mos = self.env['mrp.production'].search([
# ('origin', '=', mo.name)]) # ('origin', '=', mo.name)])
@@ -407,6 +471,28 @@ class SFSaleOrderCancelLine(models.TransientModel):
} }
lines.append(self.create(vals)) lines.append(self.create(vals))
# 检查采购申请明细
purchase_request_lines = self.env['purchase.request.line'].search([
('origin', '=', order.name)
])
if purchase_request_lines:
prl_count = 0
for purchase_request_line in purchase_request_lines:
prl_count += 1
vals = {
'wizard_id': wizard_id,
'sequence': sequence,
'category': module_name_dict[purchase_request_line._original_module],
'doc_name': purchase_request_line.request_id._description,
'doc_number': purchase_request_line.request_id.name,
'line_number': prl_count,
'product_name': f'[{purchase_request_line.product_id.default_code}] {purchase_request_line.product_id.name}',
'quantity': purchase_request_line.product_qty,
'doc_state': purchase_request_map_dict.get(purchase_request_line.request_state, purchase_request_line.request_state),
'cancel_reason': '已有异动' if purchase_request_line.request_state not in ['draft', 'cancel', 'approved'] else ''
}
lines.append(self.create(vals))
# 检查制造订单 # 检查制造订单
manufacturing_orders = self.env['mrp.production'].search([ manufacturing_orders = self.env['mrp.production'].search([
('origin', '=', order.name) ('origin', '=', order.name)

View File

@@ -12,18 +12,18 @@
<div class="alert alert-warning" role="alert"> <div class="alert alert-warning" role="alert">
<field name="display_message" readonly="1" nolabel="1"/> <field name="display_message" readonly="1" nolabel="1"/>
</div> </div>
<field name="related_docs"> <field name="related_docs" >
<tree string="下游单据" create="false" edit="false" delete="false"> <tree string="下游单据" create="false" edit="false" delete="false" attrs="{'merge_fields': 'category,doc_name,operation_type,doc_number,doc_state,cancel_reason','merge_key': 'doc_number,category'}">
<!-- <field name="sequence" string="序号"/> --> <!-- <field name="sequence" string="序号"/> -->
<field name="category" string="大类"/> <field name="category" string="大类"/>
<field name="doc_name" string="单据名称"/> <field name="doc_name" string="单据名称"/>
<field name="operation_type" string="作业类型"/> <field name="operation_type" string="作业类型"/>
<field name="doc_number" string="单据编号"/> <field name="doc_number" string="单据编号"/>
<field name="doc_state" string="单据状态"/>
<field name="cancel_reason" string="禁止取消原因"/>
<field name="line_number" string="行号"/> <field name="line_number" string="行号"/>
<field name="product_name" string="产品名称"/> <field name="product_name" string="产品名称"/>
<field name="quantity_str" string="数量"/> <field name="quantity_str" string="数量"/>
<field name="doc_state" string="单据状态"/>
<field name="cancel_reason" string="禁止取消原因"/>
</tree> </tree>
</field> </field>
<footer> <footer>

View File

@@ -117,17 +117,30 @@ class WorkpieceDeliveryWizard(models.TransientModel):
item.button_finish() item.button_finish()
# return scheduling.read()[0] # return scheduling.read()[0]
return { if isinstance(scheduling, bool) and scheduling is True:
'type': 'ir.actions.client', return{
'tag': 'display_notification', 'type': 'ir.actions.client',
'target': 'new', 'tag': 'display_notification',
'params': { 'target': 'new',
'message': f'任务下发成功AGV任务调度编号为【{scheduling.name}', 'params': {
'type': 'success', 'message': f'解除装夹成功',
'sticky': False, 'type': 'success',
'next': {'type': 'ir.actions.act_window_close'}, '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: except Exception as e:
logging.info('%s任务下发失败:%s', self.delivery_type, e) logging.info('%s任务下发失败:%s', self.delivery_type, e)
raise UserError(f'{self.delivery_type}任务下发失败:{e}') from e raise UserError(f'{self.delivery_type}任务下发失败:{e}') from e

View File

@@ -191,7 +191,8 @@ class SFMessageWork(models.Model):
def write(self, vals): def write(self, vals):
res = super(SFMessageWork, self).write(vals) res = super(SFMessageWork, self).write(vals)
if ('leave_id' in vals and vals['leave_id'] is False or 'date_planned_start' in vals and vals['date_planned_start'] is False) \ for record in self:
and self.schedule_state != '未排': if ('leave_id' in vals and vals['leave_id'] is False or 'date_planned_start' in vals and vals['date_planned_start'] is False) \
self.add_queue('计划数据异常跟踪') and record.schedule_state != '未排':
record.add_queue('计划数据异常跟踪')
return res return res

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of SmartGo. See LICENSE file for full copyright and licensing details. # Part of SmartGo. See LICENSE file for full copyright and licensing details.
import logging import logging
import traceback
import requests import requests
@@ -90,7 +91,8 @@ class ResConfigSettings(models.TransientModel):
_logger.info("同步坯料冗余完成") _logger.info("同步坯料冗余完成")
except Exception as e: except Exception as e:
_logger.info("sf_all_sync error: %s" % e) traceback_error = traceback.format_exc()
_logger.error("sf_all_sync error:%s" % traceback_error)
raise ValidationError("数据错误导致同步失败,请联系管理员") raise ValidationError("数据错误导致同步失败,请联系管理员")
@api.model @api.model

View File

@@ -250,6 +250,10 @@ class sfMaterialModel(models.Model):
materials_model.need_h = item['need_h'] materials_model.need_h = item['need_h']
materials_model.density = item['density'] materials_model.density = item['density']
materials_model.active = item['active'] materials_model.active = item['active']
materials_model.materials_code= item['materials_code']
materials_model.alloy_code = item['alloy_code']
materials_model.apply = self.env['material.apply'].search(
[("name", 'in', item['apply'])]).ids
else: else:
raise ValidationError("材料型号认证未通过") raise ValidationError("材料型号认证未通过")
@@ -3210,6 +3214,7 @@ class EmbryoRedundancySync(models.Model):
embryo_redundancy.width = item['width'] embryo_redundancy.width = item['width']
embryo_redundancy.height = item['height'] embryo_redundancy.height = item['height']
embryo_redundancy.active = item['active'] embryo_redundancy.active = item['active']
embryo_redundancy.remark = item['remark']
else: else:
self.env['sf.embryo.redundancy'].sudo().create({ self.env['sf.embryo.redundancy'].sudo().create({
"name": item['name'], "name": item['name'],
@@ -3218,4 +3223,5 @@ class EmbryoRedundancySync(models.Model):
"width": item['width'], "width": item['width'],
"height": item['height'], "height": item['height'],
"active": item['active'], "active": item['active'],
"remark": item['remark'],
}) })

View File

@@ -2,3 +2,4 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models from . import models
from . import wizard

View File

@@ -16,10 +16,16 @@
'depends': ['quality_control', 'web_widget_model_viewer', 'sf_manufacturing','jikimo_attachment_viewer'], 'depends': ['quality_control', 'web_widget_model_viewer', 'sf_manufacturing','jikimo_attachment_viewer'],
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'data/check_standards.xml',
'data/documents_data.xml',
'data/insepection_report_template.xml',
'data/report_actions.xml',
'views/view.xml', 'views/view.xml',
'views/quality_cnc_test_view.xml', 'views/quality_cnc_test_view.xml',
'views/mrp_workorder.xml', 'views/mrp_workorder.xml',
'views/quality_check_view.xml' 'views/quality_check_view.xml',
'views/quality_company.xml',
'wizard/check_picking_wizard_view.xml',
], ],
'assets': { 'assets': {

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="test_type_factory_inspection" model="quality.point.test_type">
<field name="name">出厂检验报告</field>
<field name="technical_name">factory_inspection</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- 创建出厂检验报告文件夹 -->
<record id="documents_purchase_contracts_folder" model="documents.folder">
<field name="name">出厂检验报告</field>
<field name="description">存放出厂检验报告相关文件</field>
<field name="sequence">11</field>
</record>
<!-- 创建出厂检验报告文件夹下的子文件夹-已发布 -->
<record id="documents_purchase_contracts_folder_published" model="documents.folder">
<field name="name">已发布</field>
<field name="parent_folder_id" ref="documents_purchase_contracts_folder"/>
<field name="sequence">1</field>
</record>
<!-- 创建出厂检验报告文件夹下的子文件夹-废弃 -->
<record id="documents_purchase_contracts_folder_canceled" model="documents.folder">
<field name="name">废弃</field>
<field name="parent_folder_id" ref="documents_purchase_contracts_folder"/>
<field name="sequence">2</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,340 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- 定义页眉模板 -->
<template id="report_quality_header">
<div class="header" style="position: relative; height: 180px; margin-bottom: 20px;">
<!-- Logo -->
<div style="position: absolute; top: 0; left: 0;">
<img t-if="o.company_id.favicon" t-att-src="image_data_uri(o.company_id.favicon)" style="max-height: 70px;" alt="Logo"/>
</div>
<!-- 标题 -->
<div style="position: absolute; top: 80px; left: 0; right: 0; width: 100%;">
<h2 style="margin: 0 auto; font-size: 33px; text-align: center; width: 200px;">出厂检验报告</h2>
</div>
<!-- 二维码和报告编号 -->
<div style="position: absolute; top: 0; right: 0; text-align: right;">
<t t-if="o.report_number_id">
<img t-att-src="'/report/barcode/QR/%s' % o.get_report_url()" style="width:100px;height:100px"/>
<div style="font-size: 14px; margin-top: 5px;">
报告编号:<span t-field="o.report_number_id"/>
</div>
<div style="font-size: 12px; margin-top: 5px;">
扫描二维码查看PDF报告
</div>
</t>
<t t-else="">
<img t-att-src="'/report/barcode/QR/%s' % o.get_report_url()" style="width:100px;height:100px"/>
<div style="font-size: 14px; margin-top: 5px;">
报告编号:<span>ceshi</span>
</div>
<div style="font-size: 12px; margin-top: 5px;">
扫描二维码查看PDF报告
</div>
</t>
</div>
</div>
</template>
<!-- 定义页脚模板 -->
<template id="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 class="page"/> 页/共 <span class="topage"/></span>
</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">
<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.report_quality_footer"/>
<!-- </div> -->
</div>
</t>
</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

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="action_report_quality_inspection" model="ir.actions.report">
<field name="name">出厂检验报告</field>
<field name="model">quality.check</field> <!-- 请替换为实际的模型名称 -->
<field name="report_type">qweb-pdf</field>
<field name="report_name">sf_quality.report_quality_inspection</field>
<field name="report_file">sf_quality.report_quality_inspection</field>
<field name="print_report_name">'QC-' + object.name + '.pdf'</field>
<field name="binding_model_id" ref="model_quality_check"/> <!-- 请替换为实际的模型ID引用 -->
<field name="binding_type">report</field>
<field name="attachment">'QC-'+object.name+'-'+str(object.write_date)+'.pdf'</field>
<field name="attachment_use">True</field>
</record>
<!-- 定义HTML预览报告动作 -->
<record id="action_report_quality_inspection_preview" model="ir.actions.report">
<field name="name">预览检验报告</field>
<field name="model">quality.check</field>
<field name="report_type">qweb-html</field>
<field name="report_name">sf_quality.html_report_quality_inspection</field>
<field name="binding_type">report</field>
</record>
</odoo>

View File

@@ -5,3 +5,5 @@ from . import custom_quality
from . import quality from . import quality
from . import quality_cnc_test from . import quality_cnc_test
from . import mrp_workorder from . import mrp_workorder
from . import stock
from . import quality_company

View File

@@ -1,9 +1,10 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from odoo import models, fields from odoo import models, fields, api
from odoo.exceptions import ValidationError
class SfQualityPoint(models.Model): class SfQualityPoint(models.Model):
_inherit = 'quality.point' _inherit = 'quality.point'
_rec_name = 'title'
product_ids = fields.Many2many( product_ids = fields.Many2many(
'product.product', string='适用产品', 'product.product', string='适用产品',
@@ -15,4 +16,22 @@ class SfQualityPoint(models.Model):
operation_id = fields.Many2one( operation_id = fields.Many2one(
'mrp.routing.workcenter', 'Step', check_company=True, 'mrp.routing.workcenter', 'Step', check_company=True,
domain="[('is_outsource', '=', False),('company_id', 'in', (company_id, False))]") domain="[('is_outsource', '=', False),('company_id', 'in', (company_id, False))]")
@api.onchange('test_type_id')
def _onchange_test_type_id(self):
"""
如果类型选择了出厂检验报告检查measure_on的值是否为product如果为product则类型的值不变如果
不是,则提示错误
"""
if self.test_type_id.name == '出厂检验报告':
if self.measure_on != 'product':
raise ValidationError('出厂检验报告的测量对象必须为产品')
@api.onchange('measure_on')
def _onchange_measure_on(self):
"""
如果measure_on的值变了则清空test_type_id的值
"""
if self.measure_on != 'product':
self.test_type_id = False

View File

@@ -18,7 +18,7 @@ class QualityCheck(models.Model):
('cancel', '已取消'), ], string='状态', tracking=True, store=True, ('cancel', '已取消'), ], string='状态', tracking=True, store=True,
default='none', copy=False, compute='_compute_quality_state') default='none', copy=False, compute='_compute_quality_state')
individuation_page_PTD = fields.Boolean('个性化记录(是否显示后置三元检测[PTD]页签)', related='workorder_id.individuation_page_PTD') individuation_page_list = fields.Char('个性化记录', related='workorder_id.individuation_page_list')
work_state = fields.Selection(related='workorder_id.state', string='工单状态') work_state = fields.Selection(related='workorder_id.state', string='工单状态')
processing_panel = fields.Char(related='workorder_id.processing_panel', string='加工面') processing_panel = fields.Char(related='workorder_id.processing_panel', string='加工面')
@@ -26,6 +26,7 @@ class QualityCheck(models.Model):
string='生产线') string='生产线')
equipment_id = fields.Many2one(related='workorder_id.equipment_id', string='加工设备') equipment_id = fields.Many2one(related='workorder_id.equipment_id', string='加工设备')
model_file = fields.Binary(related='workorder_id.glb_file', string='加工模型') model_file = fields.Binary(related='workorder_id.glb_file', string='加工模型')
glb_url = fields.Char(related='workorder_id.glb_url', string='加工模型')
detection_report = fields.Binary(related='workorder_id.detection_report', readonly=True, string='检测报告') detection_report = fields.Binary(related='workorder_id.detection_report', readonly=True, string='检测报告')
test_results = fields.Selection([("合格", "合格"), ("返工", "返工")], string="检测结果", test_results = fields.Selection([("合格", "合格"), ("返工", "返工")], string="检测结果",
@@ -34,8 +35,8 @@ class QualityCheck(models.Model):
[("programming", "编程"), ("cutter", "刀具"), ("clamping", "装夹"), ("operate computer", "操机"), [("programming", "编程"), ("cutter", "刀具"), ("clamping", "装夹"), ("operate computer", "操机"),
("technology", "工艺"), ("customer redrawing", "客户改图")], string="原因") ("technology", "工艺"), ("customer redrawing", "客户改图")], string="原因")
detailed_reason = fields.Text('详细原因') detailed_reason = fields.Text('详细原因')
machining_drawings = fields.Binary('2D加工图纸', related='workorder_id.machining_drawings') machining_drawings = fields.Binary('2D加工图纸', related='product_id.machining_drawings')
quality_standard = fields.Binary('质检标准', related='workorder_id.quality_standard') quality_standard = fields.Binary('质检标准', related='product_id.quality_standard')
operation_id = fields.Many2one('mrp.routing.workcenter', '作业', store=True, compute='_compute_operation_id') operation_id = fields.Many2one('mrp.routing.workcenter', '作业', store=True, compute='_compute_operation_id')
is_inspect = fields.Boolean('需送检', related='point_id.is_inspect') is_inspect = fields.Boolean('需送检', related='point_id.is_inspect')
@@ -93,7 +94,8 @@ class QualityCheck(models.Model):
raise ValidationError('请填写【判定结果】里的信息') raise ValidationError('请填写【判定结果】里的信息')
if self.test_results == '合格': if self.test_results == '合格':
raise ValidationError('请重新选择【判定结果】-【检测结果】') raise ValidationError('请重新选择【判定结果】-【检测结果】')
if self.workorder_id.routing_type != 'CNC加工' and self.workorder_id.individuation_page_PTD is False: if (self.workorder_id.routing_type != 'CNC加工' and self.workorder_id.individuation_page_list
and 'PTD' not in self.workorder_id.individuation_page_list):
self.workorder_id.production_id.write({'detection_result_ids': [(0, 0, { self.workorder_id.production_id.write({'detection_result_ids': [(0, 0, {
'rework_reason': self.reason, 'rework_reason': self.reason,
'detailed_reason': self.detailed_reason, 'detailed_reason': self.detailed_reason,

View File

@@ -12,6 +12,7 @@ class SfQualityCncTest(models.Model):
production_id = fields.Many2one(related='workorder_id.production_id', string='制造订单') production_id = fields.Many2one(related='workorder_id.production_id', string='制造订单')
product_id = fields.Many2one(related='workorder_id.product_id', string='产品') product_id = fields.Many2one(related='workorder_id.product_id', string='产品')
model_file = fields.Binary(related='workorder_id.glb_file', string='加工模型') model_file = fields.Binary(related='workorder_id.glb_file', string='加工模型')
glb_url = fields.Char(related='workorder_id.glb_url', string='加工模型')
processing_panel = fields.Char(related='workorder_id.processing_panel', string='加工面') processing_panel = fields.Char(related='workorder_id.processing_panel', string='加工面')
equipment_id = fields.Many2one(related='workorder_id.equipment_id', string='加工设备') equipment_id = fields.Many2one(related='workorder_id.equipment_id', string='加工设备')
production_line_id = fields.Many2one(related='workorder_id.production_line_id', production_line_id = fields.Many2one(related='workorder_id.production_line_id',

View File

@@ -0,0 +1,8 @@
from odoo import models, fields
# 为公司增加字段
class Company(models.Model):
_inherit = 'res.company'
factory_name = fields.Char('加工工厂')

View File

@@ -0,0 +1,59 @@
from odoo import api, models
class StockPicking(models.Model):
_inherit = 'stock.picking'
def button_validate(self):
"""
出厂检验报告上传
"""
out_quality_checks = self.env['quality.check'].search(
[('picking_id', '=', self.id), ('test_type_id.name', '=', '出厂检验报告')])
# out_quality_checks 可能存在多个
if out_quality_checks:
for out_quality_check in out_quality_checks:
if not out_quality_check.is_factory_report_uploaded:
if out_quality_check and self.state == 'assigned':
out_quality_check.upload_factory_report()
"""
调拨单若关联了质量检查单,验证调拨单时,应校验是否有不合格品,若存在,应弹窗提示:
“警告存在不合格产品XXXX n 件、YYYYY m件继续调拨请点“确认”否则请取消
"""
context = self.env.context
if not context.get('again_validate') and self.quality_check_ids.filtered(lambda qc: qc.quality_state == 'fail'):
# 回滚事务,为二次确认/取消做准备
self.env.cr.rollback()
quality_check_ids = self.quality_check_ids.filtered(lambda qc: qc.quality_state == 'fail')
product_list = list(set([quality_check_id.product_id for quality_check_id in quality_check_ids]))
fail_check_text = ''
for product_id in product_list:
check_ids = quality_check_ids.filtered(lambda qc: qc.product_id == product_id)
if all(check_id.measure_on == 'move_line' for check_id in check_ids):
number = sum(check_ids.mapped('qty_line'))
else:
number = sum(self.move_ids_without_package.filtered(
lambda ml: ml.product_id == product_id).mapped('quantity_done'))
if number == 0:
number = sum(self.move_ids_without_package.filtered(
lambda ml: ml.product_id == product_id).mapped('reserved_availability'))
if number == 0:
number = sum(self.move_ids_without_package.filtered(
lambda ml: ml.product_id == product_id).mapped('product_uom_qty'))
fail_check_text = (f'{fail_check_text}{product_id.display_name} {number}'
if fail_check_text != '' else f'{product_id.display_name} {number}')
return {
'type': 'ir.actions.act_window',
'res_model': 'picking.validate.check.wizard',
'name': '质检不合格提示',
'view_mode': 'form',
'target': 'new',
'context': {
'default_picking_id': self.id,
'default_fail_check_text': f'警告:存在不合格产品{fail_check_text},继续调拨请点“确认”,否则请取消?',
'again_validate': True}
}
res = super(StockPicking, self).button_validate()
return res

View File

@@ -73,6 +73,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_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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
73
74
75
76
77
78

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

@@ -8,11 +8,13 @@
<xpath expr="//field[@name='alert_ids']" position="after"> <xpath expr="//field[@name='alert_ids']" position="after">
<field name="production_id" invisible="1"/> <field name="production_id" invisible="1"/>
<field name="work_state" invisible="1"/> <field name="work_state" invisible="1"/>
<field name="individuation_page_PTD" invisible="1"/> <field name="individuation_page_list" invisible="1"/>
<field name="production_line_id" attrs="{'invisible': [('production_id', '=', False)]}"/> <field name="production_line_id" attrs="{'invisible': [('production_id', '=', False)]}"/>
<field name="equipment_id" attrs="{'invisible': [('production_id', '=', False)]}"/> <field name="equipment_id" attrs="{'invisible': [('production_id', '=', False)]}"/>
<field name="model_file" widget="Viewer3D" string="模型" readonly="1" force_save="1" <field name="model_file" widget="Viewer3D" string="模型" readonly="1" force_save="1"
attrs="{'invisible': ['|',('model_file', '=', False), ('production_id', '=', False)]}"/> attrs="{'invisible': ['|',('model_file', '=', False), ('production_id', '=', False)]}"/>
<field name="glb_url" widget="Viewer3D" string="模型" readonly="1" force_save="1"
attrs="{'invisible': ['|',('glb_url', '=', False), ('production_id', '=', False)]}"/>
</xpath> </xpath>
<xpath expr="//field[@name='partner_id']" position="after"> <xpath expr="//field[@name='partner_id']" position="after">
<field name="processing_panel" attrs="{'invisible': [('production_id', '=', False)]}"/> <field name="processing_panel" attrs="{'invisible': [('production_id', '=', False)]}"/>
@@ -37,11 +39,11 @@
</group> </group>
</group> </group>
</page> </page>
<page string="2D图纸" attrs="{'invisible': [('production_id', '=', False)]}"> <page string="2D加工图纸" attrs="{'invisible': [('categ_type', 'not in', ['成品', '坯料'])]}">
<field name="machining_drawings" string="" widget="adaptive_viewer"/> <field name='machining_drawings' widget="adaptive_viewer"/>
</page> </page>
<page string="客户质量标准" attrs="{'invisible': [('production_id', '=', False)]}"> <page string="质检标准" attrs="{'invisible': [('categ_type', 'not in', ['成品', '坯料'])]}">
<field name="quality_standard" string="" widget="adaptive_viewer"/> <field name='quality_standard' widget="adaptive_viewer"/>
</page> </page>
<page string="其他" <page string="其他"
attrs="{'invisible': ['|',('quality_state', 'not in', ['pass', 'fail']), ('production_id', '=', False)]}"> attrs="{'invisible': ['|',('quality_state', 'not in', ['pass', 'fail']), ('production_id', '=', False)]}">
@@ -52,6 +54,7 @@
</page> </page>
</xpath> </xpath>
<xpath expr="//header//button[@name='do_pass'][1]" position="attributes"> <xpath expr="//header//button[@name='do_pass'][1]" position="attributes">
<attribute name="attrs">{'invisible': ['|', ('is_out_check', '=', True), ('quality_state', '!=', 'none')]}</attribute>
<attribute name="string">合格</attribute> <attribute name="string">合格</attribute>
</xpath> </xpath>
<xpath expr="//header//button[@name='do_pass'][2]" position="attributes"> <xpath expr="//header//button[@name='do_pass'][2]" position="attributes">
@@ -59,12 +62,32 @@
<attribute name="string">合格</attribute> <attribute name="string">合格</attribute>
</xpath> </xpath>
<xpath expr="//header//button[@name='do_fail'][1]" position="attributes"> <xpath expr="//header//button[@name='do_fail'][1]" position="attributes">
<attribute name="attrs">{'invisible': ['|', ('is_out_check', '=', True), ('quality_state', '!=', 'none')]}</attribute>
<attribute name="string">不合格</attribute> <attribute name="string">不合格</attribute>
</xpath> </xpath>
<xpath expr="//header//button[@name='do_fail'][2]" position="attributes"> <xpath expr="//header//button[@name='do_fail'][2]" position="attributes">
<attribute name="attrs">{'invisible': ['|',('quality_state', '!=', 'pass'),('work_state','in', ('done', 'rework'))]}</attribute> <attribute name="attrs">{'invisible': ['|',('quality_state', '!=', 'pass'),('work_state','in', ('done', 'rework'))]}</attribute>
<attribute name="string">不合格</attribute> <attribute name="string">不合格</attribute>
</xpath> </xpath>
<xpath expr="//header" position="inside">
<field name="is_out_check" invisible="1"/>
<field name="publish_status" invisible="1"/>
<button name="%(sf_quality.action_report_quality_inspection_preview)d"
string="预览"
type="action"
class="oe_highlight" attrs="{'invisible': [('is_out_check', '=', False)]}"/>
<!-- --><!-- 如果还需要打印按钮 -->
<!-- <button name="%(sf_quality.action_report_quality_inspection)d" -->
<!-- string="打印报告" -->
<!-- type="action"/> -->
<!-- <button name="do_preview" string="预览" type="object" class="btn-primary" attrs="{'invisible': [('is_out_check', '=', False)]}"/> -->
<button name="do_publish" string="发布" type="object" class="btn-primary" attrs="{'invisible': ['|', ('is_out_check', '=', False), ('publish_status', '!=', 'draft')]}"/>
<!-- <button name="get_report_url" string="ceshi" type="object" class="btn-primary"/> -->
<!-- <button name="upload_factory_report" string="upload_factory_report" type="object" class="btn-primary"/> -->
<button name="do_cancel_publish" string="取消发布" type="object" class="btn-primary" confirm="确定取消发布吗?" attrs="{'invisible': ['|',('is_out_check', '=', False), ('publish_status', '!=', 'published')]}"/>
<button name="do_re_publish" string="重新发布" type="object" class="btn-primary" attrs="{'invisible': ['|', ('is_out_check', '=', False), ('publish_status', '!=', 'canceled')]}"/>
</xpath>
</field> </field>
</record> </record>

View File

@@ -87,7 +87,8 @@
<field name="product_id"/> <field name="product_id"/>
<field name="production_line_id"/> <field name="production_line_id"/>
<field name="equipment_id"/> <field name="equipment_id"/>
<field name="model_file" widget="Viewer3D"/> <field name="model_file" widget="Viewer3D" attrs="{'invisible': [('model_file', '=', False)]}"/>
<field name="glb_url" widget="Viewer3D" attrs="{'invisible': [('glb_url', '=', False)]}"/>
</group> </group>
<group> <group>
<field name="part_name"/> <field name="part_name"/>

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