Compare commits

..

203 Commits

Author SHA1 Message Date
胡尧
71eefe7bc6 处理销售单不能创建的问题 2024-10-11 15:19:03 +08:00
胡尧
b74bc62804 处理下发AGV小车任务报错的问题 2024-10-10 10:49:16 +08:00
胡尧
94f57629e5 增加空料架配送菜单权限 2024-10-09 17:23:03 +08:00
胡尧
e4c845a9f6 解决空料架配送不能正常下发AGV任务的问题 2024-10-09 14:08:38 +08:00
胡尧
d239509299 解决空料架配送不能正常下发AGV任务的问题 2024-10-09 13:53:49 +08:00
胡尧
457f0aa2ac 修复login页logo不显示的问题 2024-10-08 09:19:06 +08:00
胡嘉莹
84266364e3 Accept Merge Request #1381: (develop -> release/release_2.4)
Merge Request: 修改计划排程

Created By: @胡嘉莹
Accepted By: @胡嘉莹
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1381?initial=true
2024-09-29 17:25:11 +08:00
管欢
ea88d8284a Accept Merge Request #1380: (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/1380
2024-09-29 17:09:47 +08:00
guanhuan
d2ec420f57 域名获取修改 2024-09-29 17:05:42 +08:00
guanhuan
97d33adabd 域名获取修改 2024-09-29 17:03:49 +08:00
胡尧
c11f3cc66c Merge branch 'develop' into release/release_2.4 2024-09-29 16:51:41 +08:00
廖丹龙
6b4a7e35a9 Accept Merge Request #1375: (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/1375
2024-09-29 16:48:53 +08:00
guanhuan
11100c9260 域名获取修改 2024-09-29 16:47:35 +08:00
胡嘉莹
941c3ca43a Accept Merge Request #1379: (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/1379?initial=true
2024-09-29 16:47:10 +08:00
hujiaying
05f9528ca9 修改计划排程代码 2024-09-29 16:44:48 +08:00
杨金灵
b205945f65 Accept Merge Request #1378: (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/1378?initial=true
2024-09-29 16:43:54 +08:00
jinling.yang
c1dc28488a 修复待接单路径 2024-09-29 16:42:32 +08:00
jinling.yang
547d6608e6 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-29 15:40:50 +08:00
jinling.yang
dacb3cc076 Merge branch 'feature/消息模版初始化添加相关模版' into develop 2024-09-29 15:33:51 +08:00
胡尧
282657fbca Merge branch 'release/release_2.4' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into release/release_2.4 2024-09-29 15:32:37 +08:00
杨金灵
7e516c6f0b Accept Merge Request #1377: (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/1377
2024-09-29 15:31:28 +08:00
jinling.yang
34e2a49bef 注释部分不上的代码 2024-09-29 15:30:18 +08:00
jinling.yang
ff4bdd2f2d 还原代码 2024-09-29 15:27:27 +08:00
jinling.yang
b239fdf847 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-29 15:24:39 +08:00
jinling.yang
47feb4cf3c 消息模版初始化添加相关模版 2024-09-29 15:24:28 +08:00
胡嘉莹
63b732ff42 Accept Merge Request #1376: (develop -> release/release_2.4)
Merge Request: 排程计划bug合并

Created By: @胡嘉莹
Accepted By: @胡嘉莹
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1376?initial=true
2024-09-29 11:42:53 +08:00
胡嘉莹
50d63c28d6 Accept Merge Request #1374: (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/1374?initial=true
2024-09-29 10:36:32 +08:00
hujiaying
73b7ff7d1b 修改计划排程选择日期有生成量,不能排程问题 2024-09-29 10:33:19 +08:00
jinling.yang
ccdcd01372 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop
# Conflicts:
#	sf_message/__manifest__.py
2024-09-29 09:18:07 +08:00
liaodanlong
198296f0f8 价格计算 2024-09-27 18:04:39 +08:00
廖丹龙
f122343e31 Accept Merge Request #1373: (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/1373?initial=true
2024-09-27 18:02:25 +08:00
liaodanlong
82fa39f1a3 价格计算 2024-09-27 18:00:34 +08:00
廖丹龙
841e1b4ce2 Accept Merge Request #1372: (feature/销售和排程添加消息推送 -> develop)
Merge Request: d刀具标准库数据同步

Created By: @廖丹龙
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1372?initial=true
2024-09-27 17:46:09 +08:00
liaodanlong
1ab62d7724 d刀具标准库数据同步 2024-09-27 17:43:47 +08:00
胡尧
28a46d395b Accept Merge Request #1371: (feature/程序用刀异常提醒 -> develop)
Merge Request: 增加日志

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1371
2024-09-27 15:19:21 +08:00
胡尧
64c66f1272 增加日志 2024-09-27 15:18:27 +08:00
马广威
f8d957486b Accept Merge Request #1370: (feature/制造功能优化 -> develop)
Merge Request: 优化调整相关数据返回结构等

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1370
2024-09-27 14:47:16 +08:00
mgw
1866607967 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2024-09-27 14:41:41 +08:00
mgw
40efdf6f3b 隐藏status字段 2024-09-27 14:41:23 +08:00
胡尧
f910df3ce5 Accept Merge Request #1369: (feature/程序用刀异常提醒 -> develop)
Merge Request: 去掉多余的无效刀异常推送

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1369?initial=true
2024-09-27 14:39:55 +08:00
胡尧
c99c96a9ea 去掉多余的无效刀异常推送 2024-09-27 14:39:29 +08:00
胡尧
95cc557577 Accept Merge Request #1368: (feature/程序用刀异常提醒 -> develop)
Merge Request: 程序用刀异常提醒

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1368?initial=true
2024-09-27 14:38:10 +08:00
mgw
fa78389f47 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2024-09-27 14:31:39 +08:00
mgw
f7381c43aa 调整url的获取方式;暂时屏蔽历史日志部分 2024-09-27 14:31:22 +08:00
mgw
76c5db61da 增加24h的oee参数 2024-09-27 14:13:57 +08:00
胡尧
88ffc34a68 程序用刀异常提醒 2024-09-27 14:00:56 +08:00
禹翔辉
4f65b34aeb Accept Merge Request #1367: (feature/无效刀校验优化 -> develop)
Merge Request: cnc校验刀无效刀创建检测结果时先检测是否已经存在待处理记录

Created By: @禹翔辉
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1367?initial=true
2024-09-27 13:36:44 +08:00
yuxianghui
4a26d18b46 Merge branch 'feature/下发编程优化' into feature/无效刀校验优化 2024-09-27 13:34:10 +08:00
yuxianghui
664ac8128a cnc校验刀无效刀创建检测结果时先检测是否已经存在待处理记录 2024-09-27 13:32:55 +08:00
廖丹龙
ce285818cf Accept Merge Request #1366: (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/1366?initial=true
2024-09-27 11:55:16 +08:00
liaodanlong
6f7811a843 调拨入库、刀具组装,拆解消息推送模板预置数据 2024-09-27 11:53:55 +08:00
廖丹龙
54ed90e892 Accept Merge Request #1365: (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/1365?initial=true
2024-09-27 10:32:56 +08:00
liaodanlong
f8060113d9 产品关联数据字段修改 2024-09-27 10:14:13 +08:00
mgw
48ab891991 调整设备oee结构,去掉设备运行日志 2024-09-27 10:07:02 +08:00
guanhuan
7f384c3f56 消息模板初始数据 2024-09-27 09:36:51 +08:00
guanhuan
01a2771dfb Revert "消息模板初始数据"
This reverts commit 45b6214ddd.
2024-09-27 09:35:18 +08:00
guanhuan
45b6214ddd 消息模板初始数据 2024-09-27 09:33:17 +08:00
mgw
c96ff3f5b4 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2024-09-27 09:20:36 +08:00
mgw
d84758232e 优化值为空时的逻辑 2024-09-27 09:20:19 +08:00
廖丹龙
479953e082 Accept Merge Request #1364: (feature/销售和排程添加消息推送 -> develop)
Merge Request: r角默认值

Created By: @廖丹龙
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1364?initial=true
2024-09-27 09:12:03 +08:00
jinling.yang
27881589d4 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop
# Conflicts:
#	sf_message/__manifest__.py
#	sf_message/models/sf_message_workorder.py
2024-09-27 09:10:19 +08:00
liaodanlong
79ec3f2c91 r角默认值 2024-09-27 09:09:48 +08:00
管欢
715b4181be Accept Merge Request #1363: (feature/销售和排程添加消息推送 -> develop)
Merge Request: 删除重复we_employee_id

Created By: @管欢
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @管欢
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1363
2024-09-26 18:05:49 +08:00
liaodanlong
0ccda10c65 Merge remote-tracking branch 'origin/feature/销售和排程添加消息推送' into feature/销售和排程添加消息推送 2024-09-26 17:58:20 +08:00
liaodanlong
5b4376b5f9 r角默认值 2024-09-26 17:58:04 +08:00
guanhuan
1050f7616b 删除重复we_employee_id 2024-09-26 17:57:17 +08:00
禹翔辉
3a9cc0c09c Accept Merge Request #1362: (feature/下发编程优化 -> develop)
Merge Request: 屏蔽重新下发编程单时调用删除CAM程序用刀计划记录方法

Created By: @禹翔辉
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1362?initial=true
2024-09-26 16:38:07 +08:00
yuxianghui
4d63b90373 屏蔽重新下发编程单时调用删除CAM程序用刀计划记录方法 2024-09-26 16:36:49 +08:00
胡尧
43c4614650 Accept Merge Request #1361: (feature/程序用刀异常提醒 -> develop)
Merge Request: 屏蔽title写死JIKIMO的代码

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1361
2024-09-26 15:34:46 +08:00
胡尧
eba9ccf083 屏蔽title写死JIKIMO的代码 2024-09-26 15:33:39 +08:00
廖丹龙
947100a9d4 Accept Merge Request #1360: (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/1360?initial=true
2024-09-26 14:57:11 +08:00
liaodanlong
ed4924651a Merge remote-tracking branch 'origin/feature/销售和排程添加消息推送' into feature/销售和排程添加消息推送 2024-09-26 14:55:32 +08:00
liaodanlong
3cc2bb94fe 刀具拆解消息推送 2024-09-26 14:54:43 +08:00
管欢
8ecb3eb50a Accept Merge Request #1358: (feature/销售和排程添加消息推送 -> develop)
Merge Request: 消息通知url修改

Created By: @管欢
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @管欢
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1358
2024-09-26 14:09:10 +08:00
禹翔辉
819d89c278 Accept Merge Request #1359: (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/1359?initial=true
2024-09-26 14:06:53 +08:00
yuxianghui
66d47c60bc Merge branch 'feature/CAM优化' into feature/刀具标准库同步接口优化 2024-09-26 14:05:09 +08:00
mgw
0340563749 筛选加工时间不为空的24h数据 2024-09-26 14:04:35 +08:00
yuxianghui
271e23a67f 1、刀具标准库数据同步接口优化 2024-09-26 14:01:03 +08:00
guanhuan
de42382f58 Merge branch 'refs/heads/feature/程序用刀异常提醒' into feature/销售和排程添加消息推送
# Conflicts:
#	sf_message/models/sf_message_workorder.py
2024-09-26 13:54:28 +08:00
guanhuan
3728809d10 消息通知url修改 2024-09-26 13:43:47 +08:00
guanhuan
d4e2ace8a6 消息通知url修改 2024-09-26 12:36:00 +08:00
mgw
1d4188df7e 增加24h的故障时间返回 2024-09-26 11:11:46 +08:00
胡尧
a2323293ca Accept Merge Request #1357: (feature/程序用刀异常提醒 -> develop)
Merge Request: 无效刀判断后调用cloud发送消息推送接口

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1357
2024-09-26 10:31:26 +08:00
胡尧
29bd1c2968 解决冲突 2024-09-26 10:30:40 +08:00
胡尧
6c35ec13fd 无效刀判断后调用cloud发送消息推送接口 2024-09-26 10:28:38 +08:00
廖丹龙
83107b05e2 Accept Merge Request #1356: (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/1356?initial=true
2024-09-26 09:38:52 +08:00
liaodanlong
5fb7165306 调拨入库消息推送 2024-09-26 09:22:02 +08:00
liaodanlong
c569b60d5c 手动创建的拆解单不生成消息推送 2024-09-26 09:21:04 +08:00
mgw
bd2748659a 去掉关机率 2024-09-25 17:57:24 +08:00
jinling.yang
3ee233c0bc Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-25 17:45:34 +08:00
jinling.yang
d63081dffa 优化销售订单+工单逾期 2024-09-25 17:45:15 +08:00
马广威
0932ec95bf Accept Merge Request #1355: (feature/制造功能优化 -> develop)
Merge Request: 调整设备故障日志;增加24小时的运行数据

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1355?initial=true
2024-09-25 16:32:12 +08:00
mgw
7361da0e5d Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2024-09-25 16:29:05 +08:00
mgw
2dbddf532c 增加24小时的运行数据 2024-09-25 16:28:45 +08:00
管欢
eb867e62d8 Accept Merge Request #1354: (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/1354
2024-09-25 14:39:12 +08:00
liaodanlong
d75504960a Merge remote-tracking branch 'origin/feature/销售和排程添加消息推送' into feature/销售和排程添加消息推送 2024-09-25 14:37:05 +08:00
liaodanlong
93efbf742e 产品刀具参数物料与物料号数据关联 2024-09-25 14:36:32 +08:00
guanhuan
7c3ac138b5 工单已下发通知 2024-09-25 14:31:31 +08:00
mgw
63444d3dd1 调整默认值 2024-09-25 14:10:11 +08:00
mgw
2fcad742b8 调整alarm logs的返回数据 2024-09-25 14:08:05 +08:00
mgw
f341ef5e83 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2024-09-25 13:51:09 +08:00
mgw
d488824e3d 增加分页接口;合并日志状态相同数据 2024-09-25 13:50:50 +08:00
杨金灵
0c28700f75 Accept Merge Request #1353: (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/1353
2024-09-25 13:35:56 +08:00
禹翔辉
48d97f3e57 Accept Merge Request #1352: (feature/CAM优化 -> develop)
Merge Request: 添加CNC缺刀创建CAM日志

Created By: @禹翔辉
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1352?initial=true
2024-09-25 12:27:10 +08:00
jinling.yang
ef17c9920c 修复销售+排程消息推送 2024-09-25 12:00:46 +08:00
yuxianghui
ef643a5a72 Merge branch 'feature/程序用刀校验优化' into feature/CAM优化 2024-09-25 11:53:10 +08:00
yuxianghui
b7ff8d0bd5 添加CNC缺刀创建CAM日志 2024-09-25 11:52:29 +08:00
mgw
1e721d68bf 调整视图与增加字段 2024-09-25 11:41:23 +08:00
mgw
7454297dcd 调整设备故障日志 2024-09-25 11:24:01 +08:00
guanhuan
4e0a023c36 Merge remote-tracking branch 'origin/feature/销售和排程添加消息推送' into feature/销售和排程添加消息推送 2024-09-25 10:49:10 +08:00
guanhuan
155c5eb329 坯料采购提醒 2024-09-25 10:48:35 +08:00
jinling.yang
45718ab925 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-25 09:49:43 +08:00
廖丹龙
562c8dda8d Accept Merge Request #1350: (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/1350?initial=true
2024-09-24 17:59:43 +08:00
liaodanlong
84a37a970f 调拨入库消息推送 2024-09-24 17:57:03 +08:00
管欢
37cc02e97a Accept Merge Request #1349: (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/1349
2024-09-24 16:33:23 +08:00
guanhuan
7a8753408b 连接跳转 2024-09-24 16:23:45 +08:00
黄焱
eb853d0be9 Accept Merge Request #1345: (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/1345
2024-09-24 15:37:33 +08:00
廖丹龙
52438733d7 Accept Merge Request #1348: (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/1348?initial=true
2024-09-24 15:04:58 +08:00
liaodanlong
b986dd8473 刀具组装与拆解信息推送 2024-09-24 14:58:32 +08:00
jinling.yang
98fdb581fb Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-24 13:57:01 +08:00
胡尧
ea7380288c Accept Merge Request #1347: (feature/流程用扫码完成 -> develop)
Merge Request: 修改任务下发逻辑

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1347?initial=true
2024-09-24 11:43:54 +08:00
胡尧
9beecab21d 修改任务下发逻辑 2024-09-24 11:42:58 +08:00
jinling.yang
2e6932e054 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-24 11:39:33 +08:00
杨金灵
5ca9e028d8 Accept Merge Request #1346: (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/1346?initial=true
2024-09-24 11:19:11 +08:00
hy
64e2b71a34 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/前端样式修改 2024-09-24 11:15:10 +08:00
jinling.yang
8d52c94aed Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-24 11:15:04 +08:00
jinling.yang
9d91a1c99f 删除不要的模块 2024-09-24 11:14:55 +08:00
hy
d6fdeafef6 制造-刀具标准库界面样式显示有问题 2024-09-24 11:14:08 +08:00
guanhuan
371b3a9d65 Merge remote-tracking branch 'origin/feature/销售和排程添加消息推送' into feature/销售和排程添加消息推送 2024-09-24 10:57:02 +08:00
guanhuan
36c1c7b170 单已下发通知 2024-09-24 10:56:23 +08:00
liaodanlong
38d3a7901d 刀具组装与拆解信息推送 2024-09-24 10:23:09 +08:00
禹翔辉
2223259d13 Accept Merge Request #1344: (feature/程序用刀校验优化 -> develop)
Merge Request: CAM用刀添加编程单号字段,优化CNC程序用刀校验缺刀生成的CAM用刀计划记录的判断条件,优化tree和form视图;

Created By: @禹翔辉
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1344?initial=true
2024-09-24 09:53:35 +08:00
yuxianghui
aba270182d Merge branch 'feature/产品优化' into feature/程序用刀校验优化 2024-09-24 09:51:42 +08:00
yuxianghui
c9378fc9fe CAM用刀添加编程单号字段,优化CNC程序用刀校验缺刀生成的CAM用刀计划记录的判断条件,优化tree和form视图; 2024-09-24 09:50:44 +08:00
liaodanlong
378850682d 错误日志信息调整 2024-09-23 17:50:25 +08:00
liaodanlong
02c35803da Merge remote-tracking branch 'origin/feature/销售和排程添加消息推送' into feature/销售和排程添加消息推送 2024-09-23 17:47:30 +08:00
liaodanlong
75b60c1ec8 bom清单整体式刀具匹配条件修改 2024-09-23 17:47:03 +08:00
guanhuan
2cd4424ba1 Merge remote-tracking branch 'origin/feature/销售和排程添加消息推送' into feature/销售和排程添加消息推送 2024-09-23 17:12:26 +08:00
guanhuan
d52f0aed6e 坯料发料提醒 2024-09-23 17:11:53 +08:00
liaodanlong
d197fc5b9e 保留小数点后两位 2024-09-23 16:58:58 +08:00
guanhuan
34280fe24b 坯料发料提醒 2024-09-23 16:43:29 +08:00
胡尧
fd88a37aec Accept Merge Request #1343: (feature/wechat_message -> develop)
Merge Request: 修改屏蔽登录页footer

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1343
2024-09-23 16:38:19 +08:00
胡尧
0082a308fa 修改屏蔽登录页footer 2024-09-23 16:37:36 +08:00
胡尧
b5f5826c80 Accept Merge Request #1342: (feature/wechat_message -> develop)
Merge Request: 处理皮肤问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1342?initial=true
2024-09-23 16:10:00 +08:00
胡尧
5024a9254d 删除皮肤文件 2024-09-23 16:08:49 +08:00
马广威
2a14a630d5 Accept Merge Request #1341: (feature/制造功能优化 -> develop)
Merge Request: 调整记录匹配条件;优化排程单状态变化

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1341?initial=true
2024-09-23 14:30:57 +08:00
mgw
ef8ea2599f Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2024-09-23 14:30:09 +08:00
mgw
e9ab4270a9 调整记录匹配条件;优化排程单状态变化 2024-09-23 14:29:52 +08:00
廖丹龙
a99d651509 Accept Merge Request #1340: (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/1340
2024-09-23 14:25:01 +08:00
胡嘉莹
c54a5b36d4 Accept Merge Request #1339: (feature/update_production_line -> develop)
Merge Request: 修改代发货下发参数

Created By: @胡嘉莹
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @胡嘉莹
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1339
2024-09-23 14:24:03 +08:00
胡尧
ae844cf203 Merge branch 'develop' into feature/wechat_message 2024-09-23 14:03:18 +08:00
管欢
ec66ea76ba Accept Merge Request #1330: (feature/org_info_synchronous -> develop)
Merge Request: 用户新增企业微信id

Created By: @管欢
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @管欢
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1330
2024-09-23 13:36:06 +08:00
liaodanlong
8643c41193 代码还原 2024-09-23 13:33:05 +08:00
liaodanlong
bed0483496 Merge remote-tracking branch 'refs/remotes/origin/develop' into feature/销售和排程添加消息推送 2024-09-23 13:31:52 +08:00
liaodanlong
a648208656 Merge remote-tracking branch 'origin/feature/销售和排程添加消息推送' into feature/销售和排程添加消息推送 2024-09-23 13:31:46 +08:00
liaodanlong
98af6d0530 Merge remote-tracking branch 'refs/remotes/origin/develop' into feature/销售和排程添加消息推送 2024-09-23 13:31:27 +08:00
guanhuan
5af1953e04 坯料采购提醒 2024-09-23 13:20:31 +08:00
hujiaying
b1a08be57b bfm加工订单隐藏字段 ,待发货明细中,接单日期、发货日期顺序改变,欠单如果没有发货不要显示发货时间 、更改逾期状态逻辑。 2024-09-23 12:27:31 +08:00
马广威
15981aadf1 Accept Merge Request #1338: (feature/制造功能优化 -> develop)
Merge Request: 去掉工单明细处删除按钮;已完成工单取24小时内

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1338?initial=true
2024-09-23 12:09:20 +08:00
mgw
e19cb52e70 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2024-09-23 12:08:30 +08:00
mgw
5269d09d0e 去掉工单明细处删除按钮;已完成工单取24小时内 2024-09-23 12:08:02 +08:00
liaodanlong
e7e64720c6 消息推送 2024-09-23 11:55:28 +08:00
liaodanlong
a6701a842e 刀尖r角字段修改 2024-09-23 11:55:11 +08:00
胡尧
d697bd13a1 Accept Merge Request #1337: (feature/修改网站标题 -> develop)
Merge Request: 修改网站标题写死的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1337
2024-09-23 11:21:58 +08:00
胡尧
6e20a466ce 修改网站标题写死的问题 2024-09-23 11:21:17 +08:00
胡尧
dd5c9775fc 去掉修改网页标题的代码 2024-09-23 11:18:40 +08:00
黄焱
82c274591c Accept Merge Request #1336: (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/1336?initial=true
2024-09-23 11:00:15 +08:00
hy
4f73f57ddf 漏提交代码 2024-09-23 10:59:10 +08:00
黄焱
269141dfb2 Accept Merge Request #1335: (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/1335?initial=true
2024-09-23 10:56:37 +08:00
hy
cab0e1ce0b 顶部菜单支持多级菜单 2024-09-23 10:54:37 +08:00
禹翔辉
d4ff7ffaa9 Accept Merge Request #1334: (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/1334?initial=true
2024-09-23 10:51:00 +08:00
yuxianghui
d238d09cc3 Merge branch 'feature/刀具优化' into feature/产品优化 2024-09-23 10:49:36 +08:00
yuxianghui
4cee5213bb 产品字段值优化 2024-09-23 10:48:48 +08:00
jinling.yang
759e4a481d Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into develop 2024-09-23 10:32:27 +08:00
jinling.yang
1213afe834 Merge branch 'feature/销售和排程添加消息推送' into develop 2024-09-23 10:32:18 +08:00
杨金灵
35fda7106a Accept Merge Request #1333: (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/1333
2024-09-23 10:32:05 +08:00
jinling.yang
b8baa84270 还原代码 2024-09-23 10:30:51 +08:00
jinling.yang
6591e663b6 修复待排程的消息推送 2024-09-23 10:26:53 +08:00
jinling.yang
bad5c8d489 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/销售和排程添加消息推送 2024-09-23 08:56:59 +08:00
mgw
68f8c94332 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2024-09-23 08:55:40 +08:00
mgw
ddfb233452 修复按钮固定条件显示问题 2024-09-23 08:55:23 +08:00
jinling.yang
bbf62d2302 消息模块添加权限 2024-09-20 17:32:02 +08:00
jinling.yang
17b09c1f6d Merge branch 'feature/wechat_message' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/销售和排程添加消息推送 2024-09-20 16:57:19 +08:00
禹翔辉
e0fc70ec60 Accept Merge Request #1332: (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/1332?initial=true
2024-09-20 16:55:14 +08:00
yuxianghui
4558fc0336 Merge branch 'feature/库存优化' into feature/刀具优化 2024-09-20 16:51:39 +08:00
yuxianghui
4609ec442a 1、刀具标准库优化,整体式刀具物料产品优化;刀具同步接口优化;2、新增组装单扫描确认组装和获取测量值功能;组装单更换物料弹窗信息优化; 2024-09-20 16:50:55 +08:00
mgw
eb7d9e4168 Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2024-09-20 16:38:30 +08:00
mgw
8706e25b0d 增加24h与历史日志的数据拿取 2024-09-20 16:33:56 +08:00
黄焱
85fea64f49 Accept Merge Request #1331: (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/1331?initial=true
2024-09-20 16:31:57 +08:00
hy
f74215c9f6 修改顶部下拉菜单 2024-09-20 16:27:12 +08:00
jinling.yang
cc8906980c 销售和排程添加消息推送 2024-09-20 15:42:07 +08:00
guanhuan
56f1ba0f25 用户新增企业微信id 2024-09-20 14:24:50 +08:00
马广威
0990d73075 Accept Merge Request #1329: (feature/制造功能优化 -> develop)
Merge Request: 处理提示词制造订单重复问题;处理获取数据按钮显隐问题

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1329?initial=true
2024-09-20 11:46:38 +08:00
mgw
7d877a0cbb 处理提示词制造订单重复问题;处理获取数据按钮显隐问题 2024-09-20 11:45:23 +08:00
胡尧
e13bad8483 Merge branch 'develop' into feature/wechat_message 2024-09-20 10:43:41 +08:00
胡尧
22ebb1bbe1 增加扩展 2024-09-20 10:43:13 +08:00
廖丹龙
bc888d7984 Accept Merge Request #1328: (feature/tax_sync -> develop)
Merge Request: 刀具岗权限调整

Created By: @廖丹龙
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @廖丹龙
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1328
2024-09-20 10:42:55 +08:00
胡尧
c92d4f7868 增加扩展 2024-09-20 10:41:41 +08:00
管欢
667a2a81fb Accept Merge Request #1327: (feature/org_info_synchronous -> 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/1327
2024-09-20 10:40:26 +08:00
guanhuan
d067c5b8c4 左侧图标修改 2024-09-20 09:49:13 +08:00
jinling.yang
f6e371f223 Merge branch 'feature/wechat_message' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/new 2024-09-20 09:25:00 +08:00
胡尧
0aefe9e656 修改目录结构 2024-09-20 09:19:34 +08:00
胡尧
4f8f29e41a 新增消息通知模块 2024-09-19 17:56:00 +08:00
349 changed files with 1771 additions and 18802 deletions

View File

@@ -6,6 +6,6 @@ import { patch } from "web.utils";
patch(WebClient.prototype, "kolpolok_custom_title_and_favicon.WebClient", { patch(WebClient.prototype, "kolpolok_custom_title_and_favicon.WebClient", {
setup() { setup() {
this._super(); this._super();
this.title.setParts({ zopenerp: "JIKIMO" }); // this.title.setParts({ zopenerp: "JIKIMO" });
}, },
}); });

View File

@@ -5,9 +5,9 @@
<!-- 修改页面头部图标及文字 --> <!-- 修改页面头部图标及文字 -->
<template id="favicon_icon" inherit_id="web.layout" name="Web layout"> <template id="favicon_icon" inherit_id="web.layout" name="Web layout">
<!-- change the title with reliance partner --> <!-- change the title with reliance partner -->
<xpath expr="//head//title" position="before"> <!-- <xpath expr="//head//title" position="before">
<title t-esc="'JIKIMO'"/> <title t-esc="'JIKIMO'"/>
</xpath> </xpath> -->
<!-- change the default favicon icon with --> <!-- change the default favicon icon with -->
<xpath expr="//head//link[@rel='shortcut icon']" position="replace"> <xpath expr="//head//link[@rel='shortcut icon']" position="replace">
<link type="image/x-icon" rel="shortcut icon" href="/jikimo_frontend/static/src/img/jikimo-logo.ico"/> <link type="image/x-icon" rel="shortcut icon" href="/jikimo_frontend/static/src/img/jikimo-logo.ico"/>
@@ -16,7 +16,7 @@
<!-- hide 登录页面 powerd by odoo 及管理数据库 --> <!-- hide 登录页面 powerd by odoo 及管理数据库 -->
<template id="login_page_layout" inherit_id="web.login_layout" name="Login Page Layout"> <template id="login_page_layout" inherit_id="web.login_layout" name="Login Page Layout">
<xpath expr="//div[@class='card-body']//div[last()]" position="replace"></xpath> <!-- <xpath expr="//div[@class='card-body']/div[last()]" position="replace"></xpath> -->
</template> </template>
<!-- 隐藏odoo版本信息 --> <!-- 隐藏odoo版本信息 -->

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

View File

@@ -234,7 +234,7 @@
</record> </record>
<menuitem name="系统工单" id="work_order_1_list" web_icon="jikimo_system_order,static/description/icon.png"/> <menuitem name="系统工单" id="work_order_1_list" web_icon="jikimo_system_order,static/description/系统工单.png"/>
<menuitem name="工单" id="work_order" parent="work_order_1_list" action="system_order"/> <menuitem name="工单" id="work_order" parent="work_order_1_list" action="system_order"/>
<menuitem name="工单模板" id="work_order_template" parent="work_order_1_list" action="work_template" groups="jikimo_system_order.group_operations_permissions_rwc"/> <menuitem name="工单模板" id="work_order_template" parent="work_order_1_list" action="work_template" groups="jikimo_system_order.group_operations_permissions_rwc"/>
<menuitem name="工单分类" id="work_order_type" parent="work_order_1_list" action="classify" groups="jikimo_system_order.group_operations_permissions_rwc"/> <menuitem name="工单分类" id="work_order_type" parent="work_order_1_list" action="classify" groups="jikimo_system_order.group_operations_permissions_rwc"/>

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

View File

@@ -1024,7 +1024,7 @@
<menuitem <menuitem
id="menu_quality_root" id="menu_quality_root"
name="Quality" name="Quality"
web_icon="quality_control,static/description/icon.svg" web_icon="quality_control,static/description/质量.png"
sequence="150" sequence="150"
groups="quality.group_quality_user"/> groups="quality.group_quality_user"/>

View File

@@ -1,2 +1,3 @@
pystrich pystrich
cpca cpca
pycryptodome==3.20

View File

@@ -321,7 +321,7 @@ class ToolInventory(models.Model):
prefix = fields.Char('前缀') prefix = fields.Char('前缀')
postfix = fields.Char('后缀') postfix = fields.Char('后缀')
diameter = fields.Float('直径(mm)') diameter = fields.Float('直径(mm)')
angle = fields.Float('R角(mm)') angle = fields.Float('R角(mm)',default=0)
tool_length = fields.Float('刀具总长(mm)') tool_length = fields.Float('刀具总长(mm)')
blade_length = fields.Float('避空长/刃长(mm)') blade_length = fields.Float('避空长/刃长(mm)')
knife_head_name = fields.Char('刀头名称') knife_head_name = fields.Char('刀头名称')

View File

@@ -21,8 +21,9 @@ class ToolMaterialsBasicParameters(models.Model):
neck_length = fields.Float('颈部长度(mm)') neck_length = fields.Float('颈部长度(mm)')
handle_diameter = fields.Float('柄部直径(mm)') handle_diameter = fields.Float('柄部直径(mm)')
handle_length = fields.Float('柄部长度(mm)') handle_length = fields.Float('柄部长度(mm)')
blade_tip_diameter = fields.Integer('刀尖直径(mm)') blade_tip_diameter = fields.Float('刀尖直径(mm)')
blade_tip_working_size = fields.Char('刀尖处理尺寸(R半径mm/倒角度)', size=20) blade_tip_working_size = fields.Char('刀尖倒角度)', size=20)
tip_r_size = fields.Float('刀尖R角(mm)',default=0)
blade_tip_taper = fields.Integer('刀尖锥度(°)') blade_tip_taper = fields.Integer('刀尖锥度(°)')
blade_diameter = fields.Float('刃部直径(mm)') blade_diameter = fields.Float('刃部直径(mm)')
blade_length = fields.Float('刃部长度(mm)') blade_length = fields.Float('刃部长度(mm)')
@@ -37,7 +38,7 @@ class ToolMaterialsBasicParameters(models.Model):
width = fields.Float('宽度(mm)') width = fields.Float('宽度(mm)')
cutting_blade_length = fields.Float('切削刃长(mm)') cutting_blade_length = fields.Float('切削刃长(mm)')
relief_angle = fields.Integer('后角(°)') relief_angle = fields.Integer('后角(°)')
blade_tip_circular_arc_radius = fields.Char('刀尖圆弧半径(mm)', size=20) blade_tip_circular_arc_radius = fields.Char('刀尖圆弧半径(mm)', size=20,default='0')
inscribed_circle_diameter = fields.Float('内接圆直径IC/D(mm)') inscribed_circle_diameter = fields.Float('内接圆直径IC/D(mm)')
install_aperture_diameter = fields.Float('安装孔直径(mm)') install_aperture_diameter = fields.Float('安装孔直径(mm)')
chip_breaker_groove = fields.Selection([('', ''), ('单面', '单面'), ('双面', '双面')], chip_breaker_groove = fields.Selection([('', ''), ('单面', '单面'), ('双面', '双面')],

View File

@@ -30,6 +30,7 @@ patch(barcodeGenericHandlers, "start", {
"O-CMD.PAGER-FIRST": () => updatePager("first"), "O-CMD.PAGER-FIRST": () => updatePager("first"),
"O-CMD.PAGER-LAST": () => updatePager("last"), "O-CMD.PAGER-LAST": () => updatePager("last"),
"O-CMD.CONFIRM": () => customClickOnButton(".jikimo_button_confirm"), "O-CMD.CONFIRM": () => customClickOnButton(".jikimo_button_confirm"),
"O-CMD.FLUSHED": () => customClickOnButton(".jikimo_button_flushed"),
}; };
barcode.bus.addEventListener("barcode_scanned", (ev) => { barcode.bus.addEventListener("barcode_scanned", (ev) => {

View File

@@ -159,9 +159,6 @@ td.o_required_modifier {
display:inline; display:inline;
} }
.diameter{ .diameter{
display: flex !important;
justify-content: flex-start !important;
align-items: center !important;
} }
.o_address_format { .o_address_format {
display: flex !important; display: flex !important;

View File

@@ -15,6 +15,7 @@
<field name="handle_length"/> <field name="handle_length"/>
<field name="blade_tip_diameter"/> <field name="blade_tip_diameter"/>
<field name="blade_tip_working_size"/> <field name="blade_tip_working_size"/>
<field name="tip_r_size"/>
<field name="blade_tip_taper"/> <field name="blade_tip_taper"/>
<field name="blade_diameter"/> <field name="blade_diameter"/>
<field name="blade_length"/> <field name="blade_length"/>
@@ -95,6 +96,7 @@
<field name="handle_length"/> <field name="handle_length"/>
<field name="blade_tip_diameter"/> <field name="blade_tip_diameter"/>
<field name="blade_tip_working_size"/> <field name="blade_tip_working_size"/>
<field name="tip_r_size"/>
<field name="blade_tip_taper"/> <field name="blade_tip_taper"/>
<field name="blade_diameter"/> <field name="blade_diameter"/>
<field name="blade_length"/> <field name="blade_length"/>
@@ -139,6 +141,7 @@
</group> </group>
<group attrs="{'invisible': [('cutting_tool_type', '!=', '整体式刀具')]}"> <group attrs="{'invisible': [('cutting_tool_type', '!=', '整体式刀具')]}">
<field name="blade_tip_working_size"/> <field name="blade_tip_working_size"/>
<field name="tip_r_size"/>
<field name="blade_tip_diameter" class="diameter"/> <field name="blade_tip_diameter" class="diameter"/>
<field name="blade_tip_taper"/> <field name="blade_tip_taper"/>
<field name="blade_helix_angle"/> <field name="blade_helix_angle"/>

View File

@@ -222,6 +222,7 @@
<field name="handle_diameter" class="diameter"/> <field name="handle_diameter" class="diameter"/>
<field name="handle_length"/> <field name="handle_length"/>
<field name="blade_tip_working_size" class="du"/> <field name="blade_tip_working_size" class="du"/>
<field name="tip_r_size"/>
<field name="blade_tip_diameter" class="diameter"/> <field name="blade_tip_diameter" class="diameter"/>
<field name="blade_tip_taper" class="du"/> <field name="blade_tip_taper" class="du"/>
<field name="blade_helix_angle" class="du"/> <field name="blade_helix_angle" class="du"/>
@@ -577,7 +578,7 @@
</field> </field>
</record> </record>
<record model="ir.ui.view" id="view_cutting_tool_material_search"> <record model="ir.ui.view" id="view_cutting_tool_inventory_search">
<field name="name">sf.tool.inventory.search</field> <field name="name">sf.tool.inventory.search</field>
<field name="model">sf.tool.inventory</field> <field name="model">sf.tool.inventory</field>
<field name="arch" type="xml"> <field name="arch" type="xml">

View File

@@ -55,8 +55,7 @@ class StatusChange(models.Model):
logging.info('接口已经执行=============') logging.info('接口已经执行=============')
else: else:
traceback_error = traceback.format_exc() traceback_error = traceback.format_exc()
logging.error("bfm订单状态同步失败:%s request info %s" % traceback_error) logging.error("bfm订单状态同步失败:%s" % traceback_error)
logging.error('/api/get/state/get_order 请求失败{}'.format(ret))
raise UserError('工厂加工同步订单状态到bfm失败') raise UserError('工厂加工同步订单状态到bfm失败')
except UserError as e: except UserError as e:
traceback_error = traceback.format_exc() traceback_error = traceback.format_exc()

View File

@@ -191,6 +191,8 @@
attrs="{'invisible': [('cutting_tool_type', '!=', '整体式刀具')],'readonly': [('id', '!=', False)]}"/> attrs="{'invisible': [('cutting_tool_type', '!=', '整体式刀具')],'readonly': [('id', '!=', False)]}"/>
<field name="cutting_tool_blade_tip_working_size" <field name="cutting_tool_blade_tip_working_size"
attrs="{'invisible': [('cutting_tool_type', '!=', '整体式刀具')],'readonly': [('id', '!=', False)]}"/> attrs="{'invisible': [('cutting_tool_type', '!=', '整体式刀具')],'readonly': [('id', '!=', False)]}"/>
<field name="cutting_tool_blade_tip_r_size"
attrs="{'invisible': [('cutting_tool_type', '!=', '整体式刀具')],'readonly': [('id', '!=', False)]}"/>
<field name="cutting_tool_blade_tip_diameter" string="刀尖直径(mm)" class="diameter" <field name="cutting_tool_blade_tip_diameter" string="刀尖直径(mm)" class="diameter"
attrs="{'invisible': [('cutting_tool_type', '!=', '整体式刀具')],'readonly': [('id', '!=', False)]}"/> attrs="{'invisible': [('cutting_tool_type', '!=', '整体式刀具')],'readonly': [('id', '!=', False)]}"/>
<field name="cutting_tool_blade_tip_taper" string="刀尖锥度(°)" <field name="cutting_tool_blade_tip_taper" string="刀尖锥度(°)"

View File

@@ -7,7 +7,7 @@
'sequence': 1, 'sequence': 1,
'category': 'sf', 'category': 'sf',
'website': 'https://www.sf.jikimo.com', 'website': 'https://www.sf.jikimo.com',
'depends': ['hr'], 'depends': ['base', 'hr'],
'data': [ 'data': [
'views/hr_employee.xml', 'views/hr_employee.xml',
'views/res_config_settings_views.xml', 'views/res_config_settings_views.xml',

View File

@@ -20,7 +20,9 @@ class JkmPracticeEmployee(models.Model):
if result['employee_list']: if result['employee_list']:
for employee_info in result['employee_list']: for employee_info in result['employee_list']:
if employee_info['work_email']: if employee_info['work_email']:
self.sudo().search([('work_email', '=', employee_info['work_email'])]).write( hr_employee = self.sudo().search([('work_email', '=', employee_info['work_email'])])
{'we_id': employee_info['we_id']}) hr_employee.write({'we_id': employee_info['we_id']})
if hr_employee.user_id:
hr_employee.user_id.write({'we_employee_id': employee_info['we_id']})
else: else:
logging.info('_employee_info_sync error:%s' % result['message']) logging.info('_employee_info_sync error:%s' % result['message'])

View File

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

View File

@@ -162,7 +162,7 @@ class Sf_Dashboard_Connect(http.Controller):
# 关机时长:初次上线时间 - 通电时间 # 关机时长:初次上线时间 - 通电时间
'power_off_time': power_off_time, 'power_off_time': power_off_time,
# 关机率:关机时长/初次上线时间 # 关机率:关机时长/初次上线时间
'power_off_rate': power_off_rate, # 'power_off_rate': power_off_rate,
'first_online_duration': first_online_duration, 'first_online_duration': first_online_duration,
# 停机时间:关机时间 - 运行时间 # 停机时间:关机时间 - 运行时间
# 停机时长:关机时间 - 初次上线时间 # 停机时长:关机时间 - 初次上线时间
@@ -187,11 +187,11 @@ class Sf_Dashboard_Connect(http.Controller):
""" """
res = {'status': 1, 'message': '成功', 'data': {}} res = {'status': 1, 'message': '成功', 'data': {}}
logging.info('前端请求日志数据的参数为:%s' % kw) logging.info('前端请求日志数据的参数为:%s' % kw)
# 连接数据库
conn = psycopg2.connect(**db_config)
cur = conn.cursor()
try: try:
# 连接数据库
conn = psycopg2.connect(**db_config)
cur = conn.cursor()
machine_list = ast.literal_eval(kw['machine_list']) machine_list = ast.literal_eval(kw['machine_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('"')
@@ -231,6 +231,100 @@ class Sf_Dashboard_Connect(http.Controller):
res['message'] = '前端请求日志数据失败,原因:%s' % e res['message'] = '前端请求日志数据失败,原因:%s' % e
return json.dumps(res) return json.dumps(res)
finally:
if cur:
cur.close()
if conn:
conn.close()
@http.route('/api/logs/page_data', type='http', auth='public', methods=['GET', 'POST'],
csrf=False, cors="*")
def logs_page_data(self, **kw):
"""
拿到日志数据返回给大屏展示(支持时间戳分页)
:param kw:
:return:
"""
res = {'status': 1, 'message': '成功', 'data': {}}
logging.info('前端请求日志数据的参数为:%s' % kw)
# 连接数据库
conn = psycopg2.connect(**db_config)
cur = conn.cursor()
try:
# 获取并解析传递的参数
machine_list = ast.literal_eval(kw.get('machine_list', '[]'))
begin_time_str = kw.get('begin_time', '').strip('"')
end_time_str = kw.get('end_time', '').strip('"')
page = int(kw.get('page', 1)) # 默认页码为1
page_size = int(kw.get('page_size', 80)) # 默认每页条数为10
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
# 计算分页的 offset
offset = (page - 1) * page_size
# 先查询符合条件的总记录数
total_records = 0
for item in machine_list:
count_sql = '''
SELECT COUNT(*)
FROM device_data
WHERE device_name = %s AND time >= %s AND time <= %s;
'''
# 执行总记录数查询
cur.execute(count_sql, (item, begin_time, end_time))
record_count = cur.fetchone()[0] # 获取总记录数
total_records += record_count
# 计算总页数
if total_records > 0:
total_pages = (total_records + page_size - 1) // page_size # 向上取整
else:
total_pages = 0
# 将总页数和总记录数返回到响应中
res['total_records'] = total_records
res['total_pages'] = total_pages
for item in machine_list:
sql = '''
SELECT time, device_state, program_name
FROM device_data
WHERE device_name = %s AND time >= %s AND time <= %s
ORDER BY time DESC
LIMIT %s OFFSET %s;
'''
# 执行SQL命令使用参数绑定
cur.execute(sql, (item, begin_time, end_time, page_size, offset))
results = cur.fetchall()
# 将数据按照 equipment_code 进行分组
if item not in res['data']:
res['data'][item] = []
for result in results:
res['data'][item].append({
'time': result[0].strftime('%Y-%m-%d %H:%M:%S'),
'state': result[1],
'production_name': result[2],
})
return json.dumps(res) # 返回分页数据
except Exception as e:
logging.info('前端请求日志数据失败,原因:%s' % e)
res['status'] = -1
res['message'] = '前端请求日志数据失败,原因:%s' % e
return json.dumps(res)
finally:
if cur:
cur.close()
if conn:
conn.close()
# 返回CNC机床列表 # 返回CNC机床列表
@http.route('/api/CNCList', type='http', auth='public', methods=['GET', 'POST'], csrf=False, @http.route('/api/CNCList', type='http', auth='public', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@@ -378,7 +472,8 @@ class Sf_Dashboard_Connect(http.Controller):
pass_rate = 1 pass_rate = 1
if pass_nums: if pass_nums:
pass_rate = round( pass_rate = round(
(len(pass_nums) / detection_data if len(plan_data_finish_orders) > 0 else 0), 3) # (len(pass_nums) / detection_data if len(plan_data_finish_orders) > 0 else 0), 3)
(len(pass_nums) / len(plan_data_finish_orders) if len(plan_data_finish_orders) > 0 else 0), 3)
# 返工率 # 返工率
rework_rate = round( rework_rate = round(
@@ -418,8 +513,9 @@ class Sf_Dashboard_Connect(http.Controller):
'plan_data_progress_deviation': plan_data_progress_deviation, 'plan_data_progress_deviation': plan_data_progress_deviation,
'plan_data_rework_counts': plan_data_rework_counts, 'plan_data_rework_counts': plan_data_rework_counts,
'on_time_rate': on_time_rate, 'on_time_rate': on_time_rate,
'detection_data': detection_data, # 'detection_data': detection_data,
'pass_rate': pass_rate 'detection_data': plan_data_finish_counts,
'pass_rate': (plan_data_finish_counts - plan_data_fault_counts) / plan_data_finish_counts
} }
res['data'][line] = data res['data'][line] = data
@@ -489,11 +585,14 @@ 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
# print(start_time, end_time)
orders = plan_obj.search([('production_line_id.name', '=', line), ('state', 'in', ['finished']), orders = plan_obj.search([
(date_field_name, '>=', start_time.strftime('%Y-%m-%d 00:00:00')), ('production_line_id.name', '=', line),
(date_field_name, '<', end_time.strftime('%Y-%m-%d 00:00:00')) ('state', 'in', ['finished']),
]) (date_field_name, '>=', start_time.strftime('%Y-%m-%d %H:%M:%S')),
(date_field_name, '<=', end_time.strftime('%Y-%m-%d %H:%M:%S')) # 包括结束时间
])
# 使用小时和分钟作为键,确保每个小时的数据有独立的键 # 使用小时和分钟作为键,确保每个小时的数据有独立的键
key = start_time.strftime('%H:%M:%S') # 只取小时:分钟:秒作为键 key = start_time.strftime('%H:%M:%S') # 只取小时:分钟:秒作为键
time_count_dict[key] = len(orders) time_count_dict[key] = len(orders)
@@ -623,13 +722,19 @@ class Sf_Dashboard_Connect(http.Controller):
# 未完成订单 # 未完成订单
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']), ('active', '=', True) ('production_id.state', 'not in', ['cancel', 'done']), ('active', '=', True)
]) ])
# print(not_done_orders) # print(not_done_orders)
# 完成订单 # 完成订单
# 获取当前时间并计算24小时前的时间
current_time = datetime.now()
time_24_hours_ago = current_time - timedelta(hours=24)
finish_orders = plan_obj.search([ finish_orders = plan_obj.search([
('production_line_id.name', '=', line), ('state', 'in', ['finished']), ('production_line_id.name', '=', line), ('state', 'in', ['finished']),
('production_id.state', 'not in', ['cancel']), ('active', '=', True) ('production_id.state', 'not in', ['cancel']), ('active', '=', True),
('actual_end_time', '>=', time_24_hours_ago)
]) ])
# print(finish_orders) # print(finish_orders)
@@ -687,6 +792,8 @@ class Sf_Dashboard_Connect(http.Controller):
not_done_data.append(line_dict) not_done_data.append(line_dict)
for finish_order in finish_orders: for finish_order in finish_orders:
if not finish_order.actual_end_time:
continue
blank_name = '' blank_name = ''
try: try:
blank_name = finish_order.production_id.move_raw_ids[0].product_id.name blank_name = finish_order.production_id.move_raw_ids[0].product_id.name
@@ -707,7 +814,8 @@ class Sf_Dashboard_Connect(http.Controller):
'material': material, 'material': material,
'dimensions': dimensions, 'dimensions': dimensions,
'order_qty': finish_order.product_qty, 'order_qty': finish_order.product_qty,
'finish_time': finish_order.actual_end_time.strftime('%Y-%m-%d %H:%M:%S'), 'finish_time': finish_order.actual_end_time.strftime(
'%Y-%m-%d %H:%M:%S') if finish_order.actual_end_time else ' '
} }
done_data.append(line_dict) done_data.append(line_dict)
@@ -745,10 +853,10 @@ class Sf_Dashboard_Connect(http.Controller):
''' '''
sql2 = ''' sql2 = '''
SELECT DISTINCT ON (alarm_time) alarm_time, alarm_repair_time SELECT DISTINCT ON (alarm_start_time) alarm_time, alarm_start_time
FROM device_data FROM device_data
WHERE device_name = %s AND alarm_time IS NOT NULL WHERE device_name = %s AND alarm_start_time IS NOT NULL
ORDER BY alarm_time, time; ORDER BY alarm_start_time, time;
''' '''
# 执行SQL命令 # 执行SQL命令
@@ -764,8 +872,11 @@ class Sf_Dashboard_Connect(http.Controller):
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[0]) alarm_count.append(row[1])
total_alarm_time += abs(float(row[0])) if row[0]:
total_alarm_time += abs(float(row[0]))
else:
total_alarm_time += 0.0
if len(list(set(alarm_count))) == 1: if len(list(set(alarm_count))) == 1:
if list(set(alarm_count))[0] is None: if list(set(alarm_count))[0] is None:
alarm_count_num = 0 alarm_count_num = 0
@@ -791,54 +902,27 @@ class Sf_Dashboard_Connect(http.Controller):
""" """
查询设备的异常情况 查询设备的异常情况
""" """
res = {'status': 1, 'message': '成功', 'data': {}} res = {'status': 1, 'message': '成功', 'data': []}
logging.info('前端请求机床数据的参数为:%s' % kw) logging.info('前端请求机床数据的参数为:%s' % kw)
# 连接数据库
conn = psycopg2.connect(**db_config)
cur = conn.cursor()
try: try:
# 获取请求的机床数据 maintenance_logs_obj = request.env['sf.maintenance.logs'].sudo()
# # 获取请求的机床数据
# machine_list = ast.literal_eval(kw['machine_list']) # machine_list = ast.literal_eval(kw['machine_list'])
# idle_times = []
# idle_dict = {}
# for item in machine_list: # for item in machine_list:
sql = ''' # machine_data = equipment_obj.search([('code', '=', item)])
SELECT DISTINCT ON (alarm_time) alarm_time, alarm_message, system_date, system_time, alarm_repair_time for log in maintenance_logs_obj.search([]):
FROM device_data res['data'].append({
WHERE alarm_time IS NOT NULL 'name': log.name,
ORDER BY alarm_time, time; 'alarm_time': log.alarm_time.strftime('%Y-%m-%d %H:%M:%S'),
'fault_alarm_info': log.fault_alarm_info if log.fault_alarm_info else ' ',
'fault_process': log.fault_process if log.fault_process else ' ',
})
'''
# 执行SQL命令
cur.execute(sql)
result = cur.fetchall()
# print('result', result)
# 将查询结果转换为字典列表
data = []
for row in result:
record = {
'alarm_time': row[0],
'alarm_message': row[1],
'system_date': row[2],
'system_time': row[3],
'alarm_repair_time': row[4]
}
data.append(record)
# 将数据填充到返回结果中
res['data'] = data
# 返回统计结果
return json.dumps(res, ensure_ascii=False)
except Exception as e: except Exception as e:
print(f"An error occurred: {e}") logging.error(f"An error occurred: {e}")
return json.dumps(res)
finally: return json.dumps(res)
cur.close()
conn.close()
# 设备oee # 设备oee
@http.route('/api/OEE', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*") @http.route('/api/OEE', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*")
@@ -1155,27 +1239,80 @@ class Sf_Dashboard_Connect(http.Controller):
conn = psycopg2.connect(**db_config) conn = psycopg2.connect(**db_config)
# 获取请求的机床数据 # 获取请求的机床数据
machine_list = ast.literal_eval(kw['machine_list']) machine_list = ast.literal_eval(kw['machine_list'])
time_threshold = datetime.now() - timedelta(days=1)
alarm_last_24_time = 0.0
def fetch_result_as_dict(cursor): def fetch_result_as_dict(cursor):
"""辅助函数:将查询结果转为字典""" """辅助函数:将查询结果转为字典"""
columns = [desc[0] for desc in cursor.description] columns = [desc[0] for desc in cursor.description]
return dict(zip(columns, cursor.fetchone())) if cursor.rowcount != 0 else None return dict(zip(columns, cursor.fetchone())) if cursor.rowcount != 0 else None
# 获取当前时间的时间戳
current_timestamp = datetime.now().timestamp()
for item in machine_list: for item in machine_list:
euipment_obj = request.env['maintenance.equipment'].sudo().search([('code', '=', item)])
# 机床上线时间段
first_online_duration = current_timestamp - euipment_obj.first_online_time.timestamp()
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 device_state != '离线' AND device_state != '离线' AND process_time IS NOT NULL
ORDER BY time DESC ORDER BY time DESC
LIMIT 1; LIMIT 1;
""", (item,)) """, (item,))
last_all_time = fetch_result_as_dict(cur) last_all_time = fetch_result_as_dict(cur)
with conn.cursor() as cur:
cur.execute("""
SELECT * FROM device_data
WHERE device_name = %s
AND device_state != '离线' AND time >= %s AND process_time IS NOT NULL
ORDER BY time ASC
LIMIT 1;
""", (item, time_threshold))
last_24_time = fetch_result_as_dict(cur)
with conn.cursor() as cur:
cur.execute("""
SELECT COUNT(*)
FROM (
SELECT DISTINCT ON (idle_start_time) idle_start_time
FROM device_data
WHERE device_name = %s AND idle_start_time IS NOT NULL AND time >= %s
ORDER BY idle_start_time, time
) subquery;
""", (item, time_threshold))
idle_count = cur.fetchone()[0]
alarm_last_24_nums = []
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 AND time >= %s;
""", (item, time_threshold))
results = cur.fetchall()
for result in results:
alarm_last_24_nums.append(result[1])
if result[0]:
if float(result[0]) >= 1000:
continue
alarm_last_24_time += float(result[0])
else:
alarm_last_24_time += 0.0
# 返回数据 # 返回数据
res['data'][item] = { res['data'][item] = {
'wait_time': last_all_time['run_time'] if last_all_time['run_time'] is not None else 0, 'wait_time': last_all_time['run_time'] if last_all_time['run_time'] is not None else 0,
'cut_time': last_all_time['process_time'] if last_all_time['process_time'] is not None else 0, 'cut_time': last_all_time['process_time'] if last_all_time['process_time'] is not None else 0,
'power_on_time': last_all_time['power_on_time'] if last_all_time['power_on_time'] is not None else 0 'cut_24_time': last_24_time['process_time'] if last_24_time['process_time'] is not None else 0,
'power_on_time': last_all_time['power_on_time'] if last_all_time['power_on_time'] is not None else 0,
'power_on_24_time': last_24_time['power_on_time'] if last_24_time['power_on_time'] is not None else 0,
'alarm_last_24_time': alarm_last_24_time,
'alarm_last_24_nums': len(list(set(alarm_last_24_nums))),
'idle_count': idle_count,
'first_online_time': first_online_duration,
} }
conn.close() conn.close()

View File

@@ -285,26 +285,6 @@ class Machine_ftp(models.Model):
# # 开动率 # # 开动率
run_rate = fields.Char('开动率', readonly=True) run_rate = fields.Char('开动率', readonly=True)
# 同步CNC设备到oee
def sync_oee(self):
"""
同步CNC设备到oee
:return:
"""
for record in self:
record.ensure_one()
cnc_oee_dict = {
'equipment_id': record.id,
'type_id': record.type_id.id,
'machine_tool_picture': record.machine_tool_picture,
'equipment_code': record.code,
'function_type': record.function_type,
}
if self.env['maintenance.equipment.oee.logs'].search([('equipment_id', '=', record.id)]):
self.env['maintenance.equipment.oee.logs'].write(cnc_oee_dict)
else:
self.env['maintenance.equipment.oee.logs'].create(cnc_oee_dict)
class WorkCenterBarcode(models.Model): class WorkCenterBarcode(models.Model):
""" """

View File

@@ -14,7 +14,7 @@
<group string='状态监控'> <group string='状态监控'>
<group> <group>
<!-- <field name="timestamp"/> --> <!-- <field name="timestamp"/> -->
<field name="status"/> <!-- <field name="status"/> -->
<field name="run_status"/> <field name="run_status"/>
<field name="run_time"/> <field name="run_time"/>
<field name="system_date"/> <field name="system_date"/>

View File

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

View File

@@ -1,13 +1,14 @@
# -*-coding:utf-8-*- # -*-coding:utf-8-*-
from odoo import fields, models from odoo import fields, models, api
class SfMaintenanceLogs(models.Model): class SfMaintenanceLogs(models.Model):
_name = 'sf.maintenance.logs' _name = 'sf.maintenance.logs'
_description = '设备故障日志' _description = '设备故障日志'
_order = 'alarm_time desc'
code = fields.Char(string='编码') code = fields.Char(string='编码', readonly=True)
name = fields.Char(string='名称') name = fields.Char(string='名称', compute='_compute_name')
type = fields.Selection([('type1', '类型1'), ('type2', '类型2')], string='类型') type = fields.Selection([('type1', '类型1'), ('type2', '类型2')], string='类型')
brand = fields.Many2one('sf.machine.brand', related='maintenance_equipment_id.brand_id', string='品牌') brand = fields.Many2one('sf.machine.brand', related='maintenance_equipment_id.brand_id', string='品牌')
maintenance_equipment_id = fields.Many2one('maintenance.equipment', string='机台号') maintenance_equipment_id = fields.Many2one('maintenance.equipment', string='机台号')
@@ -28,3 +29,13 @@ class SfMaintenanceLogs(models.Model):
fault_duration = fields.Float(string='故障时长') fault_duration = fields.Float(string='故障时长')
note = fields.Text(string='备注') note = fields.Text(string='备注')
active = fields.Boolean('Active', default=True) active = fields.Boolean('Active', default=True)
@api.depends('code')
def _compute_name(self):
for record in self:
if record.code:
record.name = self.env['maintenance.equipment'].sudo().search([('code', '=', record.code), ('active', '=', True)]).name
record.maintenance_equipment_id = self.env['maintenance.equipment'].sudo().search([('code', '=', record.code), ('active', '=', True)]).id
else:
record.name = ''

View File

@@ -1,5 +1,46 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import re
import json
import datetime
import requests
from odoo import api, fields, models, _ from odoo import api, fields, models, _
from odoo.exceptions import UserError
def convert_to_seconds(time_str):
# 修改正则表达式,使 H、M、S 部分可选
if time_str is None:
return 0
pattern = r"(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?"
match = re.match(pattern, time_str)
if match:
# 提取各时间单位如果某个单位缺失则默认设为0
hours = int(match.group(1)) if match.group(1) else 0
minutes = int(match.group(2)) if match.group(2) else 0
seconds = int(match.group(3)) if match.group(3) else 0
# 计算总秒数
total_seconds = hours * 3600 + minutes * 60 + seconds
if total_seconds == 0:
# return None
pattern = r"(?:(\d+)小时)?(?:(\d+)分钟)?(?:(\d+)秒)?"
match = re.match(pattern, time_str)
if match:
# 提取各时间单位如果某个单位缺失则默认设为0
hours = int(match.group(1)) if match.group(1) else 0
minutes = int(match.group(2)) if match.group(2) else 0
seconds = int(match.group(3)) if match.group(3) else 0
# 计算总秒数
total_seconds = hours * 3600 + minutes * 60 + seconds
return total_seconds
else:
return None
return total_seconds
class SfMaintenanceEquipmentOEE(models.Model): class SfMaintenanceEquipmentOEE(models.Model):
@@ -9,24 +50,225 @@ class SfMaintenanceEquipmentOEE(models.Model):
name = fields.Char('设备oee') name = fields.Char('设备oee')
equipment_id = fields.Many2one('maintenance.equipment', '机台号', equipment_id = fields.Many2one('maintenance.equipment', '机台号',
domain="[('category_id.equipment_type', '=', '机床'),('state_zc', '=', '已注册')]") domain="[('category_id.equipment_type', '=', '机床'),('state_zc', '=', '已注册')]")
equipment_code = fields.Char('设备编码', related='equipment_id.code', store=True)
type_id = fields.Many2one('sf.machine_tool.type', '型号', related='equipment_id.type_id') type_id = fields.Many2one('sf.machine_tool.type', '型号', related='equipment_id.type_id')
machine_tool_picture = fields.Binary('设备图片', related='equipment_id.machine_tool_picture') machine_tool_picture = fields.Binary('设备图片', related='equipment_id.machine_tool_picture')
state = fields.Selection( state = fields.Selection(
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"), [("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"),
("封存(报废)", "封存(报废)")], ("封存(报废)", "封存(报废)")],
default='正常', string="机床状态", related='equipment_id.state') default='正常', string="机床状态", related='equipment_id.state')
run_time = fields.Float('加工时长(h)')
equipment_time = fields.Float('开机时长(h)') online_time = fields.Char('开机时长(小时)', reaonly='True')
done_nums = fields.Integer('加工件数')
utilization_rate = fields.Char('可用率') offline_time = fields.Char('关机时长(小时)', reaonly='True')
fault_time = fields.Float('故障时长') idle_nums = fields.Integer('待机次数', reaonly='True')
fault_nums = fields.Integer('故障次数') # 待机时长
# 故障率
fault_rate = fields.Char('故障率') idle_time = fields.Char('待机时长(小时)', reaonly='True')
# 待机率
idle_rate = fields.Char('待机率(%)', reaonly='True')
work_time = fields.Char('加工时长(小时)', reaonly='True')
work_rate = fields.Char('可用率(%)', reaonly='True')
fault_time = fields.Char('故障时长(小时)', reaonly='True')
fault_rate = fields.Char('故障率(%)', reaonly='True')
fault_nums = fields.Integer('故障次数', reaonly='True')
# 设备故障日志 # 设备故障日志
sf_maintenance_logs_ids = fields.One2many('sf.maintenance.logs', 'maintenance_equipment_oee_id', '设备故障日志', sf_maintenance_logs_ids = fields.One2many('sf.maintenance.logs', 'maintenance_equipment_oee_id', '设备故障日志',
related='equipment_id.sf_maintenance_logs_ids') related='equipment_id.sf_maintenance_logs_ids')
oee_logs = fields.One2many('maintenance.equipment.oee.logs', 'equipment_oee_id', string='运行日志')
day_logs_detail = fields.Html('日运行日志详情')
history_logs_detail = fields.Html('历史运行日志详情')
begin_time = fields.Date('开始时间')
end_time = fields.Date('结束时间')
# 获取日志详情
def get_day_logs(self):
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
print(base_url)
config = self.env['ir.config_parameter'].sudo()
# url = 'http://172.16.10.112:8069/api/logs/list'
# url_time = 'http://localhost:9069/api/RunningTimeDetail'
url = base_url + '/api/logs/list'
url_time = base_url + '/api/RunningTimeDetail'
machine_list = [self.equipment_code]
begin_time = datetime.datetime.now().strftime('%Y-%m-%d') + ' 00:00:00'
end_time = datetime.datetime.now().strftime('%Y-%m-%d') + ' 23:59:59'
# 请求的数据
data = {
"machine_list": str(machine_list),
"begin_time": begin_time,
"end_time": end_time
}
data_time = {
"machine_list": str(machine_list)
}
print(data)
# 发送POST请求
response = requests.post(url, json={}, data=data)
response_time = requests.post(url_time, json={}, data=data_time)
# print(response.json()) # 输出服务器返回的响应
print(response_time.json())
if response_time.status_code == 200:
result_time = response_time.json()
real_dict = result_time['data'][self.equipment_code]
print('=', result_time)
if result_time['status'] == 1:
self.online_time = round((convert_to_seconds(real_dict['power_on_time']) - convert_to_seconds(
real_dict['power_on_24_time'])) / 3600, 2)
self.work_time = round(
(convert_to_seconds(real_dict['cut_time']) - convert_to_seconds(real_dict['cut_24_time'])) / 3600,
2)
self.offline_time = 24 - (float(self.online_time) if self.online_time else 0)
self.idle_time = float(self.online_time) - float(
self.work_time) if self.online_time and self.work_time else 0
self.idle_rate = round(
float(self.idle_time) / (float(self.online_time) if self.online_time else 1) * 100, 2)
self.work_rate = round(
float(self.work_time) / (float(self.online_time) if self.online_time else 1) * 100, 2)
self.fault_time = (float(real_dict['alarm_last_24_time']) if real_dict[
'alarm_last_24_time'] else 0) / 3600
self.fault_rate = round(
float(self.fault_time) / (float(self.online_time) if self.online_time else 1) * 100, 2)
self.fault_nums = real_dict['alarm_last_24_nums']
self.idle_nums = real_dict['idle_count']
if response.status_code == 200:
result = response.json()
print('============', result)
if result['status'] == 1:
logs_list = result['data'][self.equipment_code]
logs_detail = ''
log_state = ''
for log in logs_list:
if log['state'] != log_state:
print('loooooooooooooooooooogs', log)
production_name = log['production_name'] if log['production_name'] else ' '
logs_detail += '<tr><td>' + log['time'] + '</td><td>' + log[
'state'] + '</td><td>' + production_name + '</td></tr>'
log_state = log['state']
# self.day_logs_detail = '<table><tr><th>时间</th><th>事件/状态</th><th>加工工单</th></tr>' + logs_detail + '</table>'
self.day_logs_detail = '''
<table border="1" style="border-collapse: collapse; width: 100%; text-align: center;">
<tr style="background-color: #f2f2f2;">
<th style="padding: 8px; border: 1px solid #ddd;">时间</th>
<th style="padding: 8px; border: 1px solid #ddd;">事件/状态</th>
<th style="padding: 8px; border: 1px solid #ddd;">加工工单</th>
</tr>
{logs_detail}
</table>
'''.format(logs_detail=logs_detail)
else:
self.day_logs_detail = '获取日志失败'
else:
self.day_logs_detail = '获取日志失败'
# 获取历史日志详情
def get_history_logs(self):
config = self.env['ir.config_parameter'].sudo()
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
# url = 'http://172.16.10.112:8069/api/logs/list'
url = base_url + '/api/logs/list'
url_time = base_url + '/api/RunningTimeDetail'
machine_list = [self.equipment_code]
if not self.begin_time:
raise UserError('请选择开始时间')
if not self.end_time:
raise UserError('请选择结束时间')
begin_time = self.begin_time.strftime('%Y-%m-%d') + ' 00:00:00'
end_time = self.end_time.strftime('%Y-%m-%d') + ' 23:59:59'
# 请求的数据
data = {
"machine_list": str(machine_list),
"begin_time": begin_time,
"end_time": end_time
}
print(data)
# 发送POST请求
response = requests.post(url, json={}, data=data)
print(response.json()) # 输出服务器返回的响应
if response.status_code == 200:
result = response.json()
print('============', result)
if result['status'] == 1:
logs_list = result['data'][self.equipment_code]
logs_detail = ''
log_state = ''
for log in logs_list:
if log['state'] != log_state:
production_name = log['production_name'] if log['production_name'] else ' '
logs_detail += '<tr><td>' + log['time'] + '</td><td>' + log[
'state'] + '</td><td>' + production_name + '</td></tr>'
log_state = log['state']
# self.day_logs_detail = '<table><tr><th>时间</th><th>事件/状态</th><th>加工工单</th></tr>' + logs_detail + '</table>'
self.history_logs_detail = '''
<table border="1" style="border-collapse: collapse; width: 100%; text-align: center;">
<tr style="background-color: #f2f2f2;">
<th style="padding: 8px; border: 1px solid #ddd;">时间</th>
<th style="padding: 8px; border: 1px solid #ddd;">事件/状态</th>
<th style="padding: 8px; border: 1px solid #ddd;">加工工单</th>
</tr>
{logs_detail}
</table>
'''.format(logs_detail=logs_detail)
else:
self.history_logs_detail = '获取日志失败'
else:
self.history_logs_detail = '获取日志失败'
# 下载历史日志
def download_history_logs(self):
config = self.env['ir.config_parameter'].sudo()
url = 'http://172.16.10.112:8069/api/logs/list'
machine_list = [self.equipment_code]
if not self.begin_time:
raise UserError('请选择开始时间')
if not self.end_time:
raise UserError('请选择结束时间')
begin_time = self.begin_time.strftime('%Y-%m-%d') + ' 00:00:00'
end_time = self.end_time.strftime('%Y-%m-%d') + ' 23:59:59'
# 请求的数据
data = {
"machine_list": str(machine_list),
"begin_time": begin_time,
"end_time": end_time
}
print(data)
# 发送POST请求
response = requests.post(url, json={}, data=data)
print(response.json()) # 输出服务器返回的响应
if response.status_code == 200:
result = response.json()
print('============', result)
if result['status'] == 1:
logs_list = result['data'][self.equipment_code]
logs_detail = ''
for log in logs_list:
production_name = log['production_name'] if log['production_name'] else ' '
# todo 下载日志
else:
self.history_logs_detail = '下载日志失败'
else:
self.history_logs_detail = '下载日志失败'
def name_get(self): def name_get(self):
result = [] result = []
@@ -95,4 +337,3 @@ class SfMaintenanceEquipmentOEELogDetail(models.Model):
log_id = fields.Many2one('maintenance.equipment.oee.logs', '日志') log_id = fields.Many2one('maintenance.equipment.oee.logs', '日志')
# equipment_code = fields.Char('设备编码', related='log_id.equipment_code') # equipment_code = fields.Char('设备编码', related='log_id.equipment_code')
equipment_code = fields.Char('设备编码', readonly='True') equipment_code = fields.Char('设备编码', readonly='True')

View File

@@ -67,6 +67,3 @@ access_sf_cutting_tool_type_admin_sf_group_equipment_user,sf_cutting_tool_type_a
access_sf_cutting_tool_type_group_purchase_director_sf_group_equipment_user,sf_cutting_tool_type_group_purchase_director,sf_base.model_sf_cutting_tool_type,sf_maintenance.sf_group_equipment_user,1,0,0,0 access_sf_cutting_tool_type_group_purchase_director_sf_group_equipment_user,sf_cutting_tool_type_group_purchase_director,sf_base.model_sf_cutting_tool_type,sf_maintenance.sf_group_equipment_user,1,0,0,0
access_sf_cutting_tool_type_group_sale_director_sf_group_equipment_user,sf_cutting_tool_type_group_sale_director,sf_base.model_sf_cutting_tool_type,sf_maintenance.sf_group_equipment_user,1,0,0,0 access_sf_cutting_tool_type_group_sale_director_sf_group_equipment_user,sf_cutting_tool_type_group_sale_director,sf_base.model_sf_cutting_tool_type,sf_maintenance.sf_group_equipment_user,1,0,0,0
access_sf_cutting_tool_type_group_plan_director_sf_group_equipment_user,sf_cutting_tool_type_group_plan_director,sf_base.model_sf_cutting_tool_type,sf_maintenance.sf_group_equipment_user,1,0,0,0 access_sf_cutting_tool_type_group_plan_director_sf_group_equipment_user,sf_cutting_tool_type_group_plan_director,sf_base.model_sf_cutting_tool_type,sf_maintenance.sf_group_equipment_user,1,0,0,0
access_maintenance_equipment_oee_logs,maintenance_equipment_oee_logs,model_maintenance_equipment_oee_logs,sf_maintenance.sf_group_equipment_manager,1,1,1,1
access_maintenance_equipment_oee_log_detail,maintenance_equipment_oee_log_detail,model_maintenance_equipment_oee_log_detail,sf_maintenance.sf_group_equipment_manager,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
67
68
69

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

View File

@@ -8,13 +8,13 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree> <tree>
<field name="equipment_id"/> <field name="equipment_id"/>
<field name="equipment_time"/> <field name="online_time"/>
<field name="run_time"/> <field name="work_time"/>
<field name="done_nums"/> <field name="work_rate"/>
<field name="utilization_rate"/>
<field name="fault_time"/> <field name="fault_time"/>
<field name="fault_nums"/> <field name="fault_nums"/>
<field name="fault_rate"/> <field name="fault_rate"/>
</tree> </tree>
</field> </field>
</record> </record>
@@ -37,32 +37,102 @@
<group> <group>
<group> <group>
<field name="type_id" readonly="1"/> <group>
<field name="equipment_time"/> <field name="equipment_id" domain="[('name','ilike','加工中心')]"/>
<field name="run_time"/> <field name="type_id"/>
<field name="done_nums"/> <field name="state"/>
<field name="utilization_rate"/> <field name="equipment_code"/>
<field name="fault_time"/> </group>
<field name="fault_nums"/>
</group> </group>
<group> <group>
<field name="machine_tool_picture" widget="image" readonly="1"/> <group>
<!-- <field name="state" nolabel="1"/> -->
<!-- <field name="state" string=""/> -->
</group>
<group>
<field name="machine_tool_picture" widget="image" nolabel="1"/>
</group>
</group>
</group>
<group>
<group>
<group>
<field name="online_time" readonly="1"/>
<field name="offline_time" readonly="1"/>
<field name="fault_rate" readonly="1"/>
</group>
<group>
<field name="idle_nums" readonly="1"/>
<field name="fault_time" readonly="1"/>
<field name="fault_nums" readonly="1"/>
</group>
</group>
<group>
<group>
<field name="idle_time"/>
<field name="idle_rate"/>
</group>
<group>
<field name="work_time"/>
<field name="work_rate"/>
</group>
</group> </group>
</group> </group>
<!-- <notebook> --> <notebook>
<!-- <page string="运行日志"> --> <page string="24H日志详情">
<!-- <field name="oee_logs"> --> <group>
<!-- <tree create="1" edit="1" delete="1" editable="bottom"> --> <button name="get_day_logs" type="object" string="查看24H日志" t-attf-style="white-space:nowrap;"/>
<!-- <field name = 'run_time'/> --> </group>
<!-- <field name = 'state'/> --> <field name="day_logs_detail" readonly="1" widget="html"/>
<!-- <field name = 'workorder_id'/> --> <!-- <field name="page_num"/> -->
<!-- <field name = 'time'/> --> <!-- <group> -->
<!-- <field name = 'color' widget="color"/> --> <!-- <group> -->
<!-- </tree> --> <!-- <button name="previous_day_logs" type="object" string="上一页" t-attf-style="white-space:nowrap;"/> -->
<!-- </field> --> <!-- </group> -->
<!-- <group> -->
<!-- <button name="next_day_logs" type="object" string="下一页" t-attf-style="white-space:nowrap;"/> -->
<!-- </group> -->
<!-- </group> -->
<!-- <field name="day_logs_detail"/> -->
<!-- <field name="day_logs_detail" widget="html" nolabel="1"/> -->
<!-- <field name="day_logs_detail" widget="html" nolabel="1" class="oe_form_field oe_form_field_html"/> -->
<!-- <field name="day_logs_detail" widget="html" nolabel="1" class="oe_form_field oe_form_field_html" options='{"class": "o_form_readonly"}'/> -->
<!-- <field name="day_logs_detail" widget="html" nolabel="1" class="oe_form_field oe_form_field_html" options='{"class": "o_form_readonly", "readonly": true, "style": "width: 100%; height: 400px;"}'/> -->
<!-- <group> -->
<!-- <div class="oe_html_field"> -->
<!-- <div id="pagination_day_logs"> -->
<!-- <button id="prev_page_day_logs" disabled="true">Previous</button> -->
<!-- <button id="next_page_day_logs">Next</button> -->
<!-- </div> -->
<!-- </div> -->
<!-- </group> -->
</page>
<!-- <page string="历史日志详情"> -->
<!-- <group> -->
<!-- <group> -->
<!-- <group> -->
<!-- <field name="begin_time"/> -->
<!-- </group> -->
<!-- <group> -->
<!-- <field name="end_time"/> -->
<!-- </group> -->
<!-- </group> -->
<!-- <group> -->
<!-- <group> -->
<!-- <button name="get_history_logs" type="object" string="查看历史日志" t-attf-style="white-space:nowrap;"/> -->
<!-- </group> -->
<!-- <group> -->
<!-- <button name="download_history_logs" type="object" string="下载历史日志" t-attf-style="white-space:nowrap;"/> -->
<!-- </group> -->
<!-- </group> -->
<!-- </group> -->
<!-- <field name="history_logs_detail"/> -->
<!-- </page> --> <!-- </page> -->
<!-- </notebook> --> </notebook>
</sheet> </sheet>
</form> </form>
</field> </field>

View File

@@ -8,8 +8,8 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree> <tree>
<field name="type" optional="hide"/> <field name="type" optional="hide"/>
<field name="brand"/> <!-- <field name="brand"/> -->
<field name="maintenance_equipment_id"/> <field name="name"/>
<field name="code_location" optional="hide"/> <field name="code_location" optional="hide"/>
<field name="fault_code" optional="hide"/> <field name="fault_code" optional="hide"/>
<field name="alarm_time"/> <field name="alarm_time"/>
@@ -37,13 +37,14 @@
<sheet> <sheet>
<div class="oe_title"> <div class="oe_title">
<h1> <h1>
<field name="code" readonly="1"/> <field name="name" readonly="1"/>
</h1> </h1>
</div> </div>
<group> <group>
<group> <group>
<!-- <field name="name"/> -->
<field name="code"/>
<!-- <field name="type" required="1" widget="radio" options="{'horizontal': true}"/> --> <!-- <field name="type" required="1" widget="radio" options="{'horizontal': true}"/> -->
<field name="maintenance_equipment_id"/> <field name="maintenance_equipment_id"/>
<field name="brand"/> <field name="brand"/>
@@ -57,7 +58,6 @@
</group> </group>
<group> <group>
<field name="operator"/> <field name="operator"/>
<field name="fault_process"/> <field name="fault_process"/>
<!-- <field name="alarm_way" required="1" widget="radio" options="{'horizontal': true}"/> --> <!-- <field name="alarm_way" required="1" widget="radio" options="{'horizontal': true}"/> -->
<field name="recovery_time"/> <field name="recovery_time"/>
@@ -105,249 +105,6 @@
</field> </field>
</record> </record>
<!-- 设备运行日志 -->
<record id="view_maintenance_logs_run_tree" model="ir.ui.view">
<field name="name">maintenance.logs.run.tree</field>
<field name="model">maintenance.equipment.oee.logs</field>
<field name="arch" type="xml">
<tree>
<field name="equipment_id"/>
</tree>
</field>
</record>
<record id="view_maintenance_logs_run_form" model="ir.ui.view">
<field name="name">maintenance.logs.run.form</field>
<field name="model">maintenance.equipment.oee.logs</field>
<field name="arch" type="xml">
<!-- <form string="设备运行日志"> -->
<!-- <header> -->
<!-- <field name="equipment_id" readonly="1"/> -->
<!-- </header> -->
<!-- <sheet> -->
<!-- <div class="oe_title"> -->
<!-- <h1> -->
<!-- <field name="start_time" readonly="1"/> -->
<!-- </h1> -->
<!-- </div> -->
<!-- <group> -->
<!-- <group> -->
<!-- <field name="stop_time" readonly="1"/> -->
<!-- <field name="duration" readonly="1"/> -->
<!-- <field name="oee" readonly="1"/> -->
<!-- </group> -->
<!-- <group> -->
<!-- <field name="note"/> -->
<!-- </group> -->
<!-- </group> -->
<!-- </sheet> -->
<!-- </form> -->
<form string="设备运行日志">
<!-- <header> -->
<!-- <field name="name" readonly="1"/> -->
<!-- </header> -->
<sheet>
<div class="oe_title">
<h1>
<field name="name"/>
</h1>
</div>
<group>
<group>
<group>
<field name="equipment_id" domain="[('name','ilike','加工中心')]"/>
<field name="type_id"/>
<field name="state"/>
<field name="equipment_code"/>
<field name="function_type"/>
</group>
</group>
<group>
<group>
<!-- <field name="state" nolabel="1"/> -->
<field name="state" string=""/>
</group>
<group>
<field name="machine_tool_picture" widget="image" nolabel="1"/>
</group>
</group>
</group>
<group>
<group>
<group>
<field name="online_time" readonly="1"/>
<field name="offline_time" readonly="1"/>
<field name="fault_rate" readonly="1"/>
</group>
<group>
<field name="offline_nums" readonly="1"/>
<field name="fault_time" readonly="1"/>
<field name="fault_nums" readonly="1"/>
</group>
</group>
<group>
<group>
<field name="idle_time"/>
<field name="idle_rate"/>
</group>
<group>
<field name="work_time"/>
<field name="work_rate"/>
</group>
</group>
</group>
<notebook>
<page string="24H日志详情">
<!-- 筛选出24小时内的日志 -->
<!-- <field name="detail_ids" domain="[('time','&lt;',(datetime.datetime.now() - datetime.timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S'))]"> -->
<field name="detail_ids" domain="[('state','ilike','加工')]">
<tree>
<!-- <field name="sequence"/> -->
<field name="time"/>
<field name="state"/>
<field name="production_name"/>
</tree>
<!-- <form> -->
<!-- <field name="sequence"/> -->
<!-- <field name="time"/> -->
<!-- <field name="state"/> -->
<!-- <field name="production_id"/> -->
<!-- </form> -->
</field>
</page>
<page string="历史日志详情">
<field name="detail_ids">
<tree>
<!-- <field name="sequence"/> -->
<field name="time"/>
<field name="state"/>
<field name="production_name"/>
</tree>
<!-- <form> -->
<!-- <field name="sequence"/> -->
<!-- <field name="time"/> -->
<!-- <field name="state"/> -->
<!-- <field name="production_id"/> -->
<!-- </form> -->
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<!-- <record id="view_maintenance_logs_run_search" model="ir.ui.view"> -->
<!-- <field name="name">maintenance.logs.run.search</field> -->
<!-- <field name="model">maintenance.equipment.oee.logs</field> -->
<!-- <field name="arch" type="xml"> -->
<!-- <search> -->
<!-- <field name="equipment_id"/> -->
<!-- <field name="start_time"/> -->
<!-- <field name="stop_time"/> -->
<!-- <field name="duration"/> -->
<!-- <field name="oee"/> -->
<!-- <field name="note"/> -->
<!-- </search> -->
<!-- </field> -->
<!-- </record> -->
<!-- 设备运行日志详情 -->
<record id="view_maintenance_logs_run_detail_tree" model="ir.ui.view">
<field name="name">maintenance.logs.run.detail.tree</field>
<field name="model">maintenance.equipment.oee.log.detail</field>
<field name="arch" type="xml">
<tree>
<!-- <field name="sequence"/> -->
<field name="time"/>
<field name="state"/>
<field name="production_name"/>
</tree>
</field>
</record>
<record id="view_maintenance_logs_run_detail_form" model="ir.ui.view">
<field name="name">maintenance.logs.run.detail.form</field>
<field name="model">maintenance.equipment.oee.log.detail</field>
<field name="arch" type="xml">
<form string="设备运行日志详情">
<sheet>
<group>
<group>
<field name="state"/>
<!-- <field name="production_id"/> -->
</group>
<group>
<!-- <field name="sequence"/> -->
<field name="time"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- <record id="view_maintenance_logs_run_detail_search" model="ir.ui.view"> -->
<!-- <field name="name">maintenance.logs.run.detail.search</field> -->
<!-- <field name="model">maintenance.equipment.oee.logs.detail</field> -->
<!-- <field name="arch" type="xml"> -->
<!-- <search> -->
<!-- <field name="equipment_id"/> -->
<!-- <field name="start_time"/> -->
<!-- <field name="stop_time"/> -->
<!-- <field name="duration"/> -->
<!-- <field name="oee"/> -->
<!-- <field name="note"/> -->
<!-- </search> -->
<!-- </field> -->
<!-- </record> -->
<!-- 设备运行日志详情action -->
<!-- <record id="action_maintenance_logs_run_detail" model="ir.actions.act_window"> -->
<!-- <field name="name">设备运行日志详情</field> -->
<!-- <field name="type">ir.actions.act_window</field> -->
<!-- <field name="res_model">maintenance.equipment.oee.logs.detail</field> -->
<!-- <field name="view_mode">tree,form</field> -->
<!-- <field name="view_id" ref="view_maintenance_logs_run_detail_tree"/> -->
<!-- <field name="help" type="html"> -->
<!-- <p class="oe_view_nocontent_create"> -->
<!-- 设备运行日志详情 -->
<!-- </p> -->
<!-- </field> -->
<!-- -->
<!-- </record> -->
<record id="action_maintenance_logs_run" model="ir.actions.act_window">
<field name="name">设备运行日志</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">maintenance.equipment.oee.logs</field>
<!-- <field name="search_view_id" ref="view_maintenance_logs_run_search"/> -->
<field name="view_mode">tree,form</field>
<!-- <field name="view_mode">form</field> -->
<field name="view_id" ref="view_maintenance_logs_run_tree"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
设备运行日志
</p>
</field>
</record>
<menuitem name="设备运行日志" id="menu_maintenance_logs_run" parent="maintenance.menu_m_request"
sequence="10" action="action_maintenance_logs_run"/>
<!-- Action --> <!-- Action -->
<record id="action_maintenance_logs" model="ir.actions.act_window"> <record id="action_maintenance_logs" model="ir.actions.act_window">

View File

@@ -1230,5 +1230,9 @@
action="hr_equipment_action1" action="hr_equipment_action1"
sequence="0"/> sequence="0"/>
<menuitem
id="maintenance.menu_maintenance_title"
web_icon="sf_maintenance,static/description/维护.png"/>
</odoo> </odoo>

View File

@@ -156,17 +156,26 @@ class AgvScheduling(models.Model):
if agv_site_state == '空闲': if agv_site_state == '空闲':
# 查询终点接驳站为agv_site_id的AGV路线 # 查询终点接驳站为agv_site_id的AGV路线
task_routes = self.env['sf.agv.task.route'].sudo().search([('end_site_id', '=', agv_site_id)]) task_routes = self.env['sf.agv.task.route'].sudo().search([('end_site_id', '=', agv_site_id)])
agv_scheduling = self.env['sf.agv.scheduling'].sudo().search( agv_schedulings = self.env['sf.agv.scheduling'].sudo().search(
[('state', '=', '待下发'), ('agv_route_type', 'in', task_routes.mapped('route_type'))], [('state', '=', '待下发'), ('agv_route_type', 'in', task_routes.mapped('route_type'))],
order='id asc', order='id asc',
limit=1
) )
task_route = task_routes.filtered( for agv_scheduling in agv_schedulings:
lambda r: r.start_site_id == agv_scheduling.start_site_id and r.start_site_id == agv_scheduling.start_site_id # 找到所有起点接驳站匹配的路线
) start_matched_task_routes = task_routes.filtered(
if task_route: lambda r: r.start_site_id == agv_scheduling.start_site_id
# 下发AGV调度任务并修改接驳站状态为占用 )
agv_scheduling.dispatch_scheduling(task_route) # 如果调度任务有终点接驳站,找到终点接驳站匹配的路线
if agv_scheduling.end_site_id:
matched_task_routes = start_matched_task_routes.filtered(
lambda r: r.end_site_id == agv_scheduling.end_site_id
)
else:
matched_task_routes = start_matched_task_routes
if matched_task_routes:
# 下发AGV调度任务并修改接驳站状态为占用
agv_scheduling.dispatch_scheduling(matched_task_routes[0])
break;
def _delivery_avg(self): def _delivery_avg(self):
config = self.env['res.config.settings'].get_values() config = self.env['res.config.settings'].get_values()

View File

@@ -990,8 +990,8 @@ class MrpProduction(models.Model):
panel_workorder.cmm_ids.sudo().unlink() panel_workorder.cmm_ids.sudo().unlink()
if panel_workorder.cnc_ids: if panel_workorder.cnc_ids:
panel_workorder.cnc_ids.sudo().unlink() panel_workorder.cnc_ids.sudo().unlink()
self.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan( # self.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan(
production) # production)
# program_path_tmp_panel = os.path.join('C://Users//43484//Desktop//fsdownload//test', # program_path_tmp_panel = os.path.join('C://Users//43484//Desktop//fsdownload//test',
# processing_panel) # processing_panel)
logging.info('program_path_tmp_panel:%s' % program_path_tmp_panel) logging.info('program_path_tmp_panel:%s' % program_path_tmp_panel)

View File

@@ -1,4 +1,5 @@
import datetime import datetime
import logging
from datetime import timedelta, time from datetime import timedelta, time
from collections import defaultdict from collections import defaultdict
from odoo import fields, models, api from odoo import fields, models, api
@@ -6,6 +7,8 @@ from odoo.addons.resource.models.resource import Intervals
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError, ValidationError
import math import math
_logger = logging.getLogger(__name__)
class ResWorkcenter(models.Model): class ResWorkcenter(models.Model):
_name = "mrp.workcenter" _name = "mrp.workcenter"
@@ -163,6 +166,19 @@ class ResWorkcenter(models.Model):
else: else:
record.effective_working_hours_day = 0 record.effective_working_hours_day = 0
# 计算传入时间日有效工作时长
def _compute_effective_working_hours_day1(self, date):
effective_working_hours_day = 0
for record in self:
attendance_ids = [p for p in record.resource_calendar_id.attendance_ids if
p.dayofweek == self.get_current_day_of_week(date)]
if attendance_ids:
for attendance_id in attendance_ids:
if attendance_id.hour_from and attendance_id.hour_to:
effective_working_hours_day += attendance_id.hour_to - attendance_id.hour_from
return effective_working_hours_day
# 获取传入时间是星期几 # 获取传入时间是星期几
def get_current_day_of_week(self, datetime): def get_current_day_of_week(self, datetime):
day_num = datetime.weekday() day_num = datetime.weekday()
@@ -211,7 +227,12 @@ class ResWorkcenter(models.Model):
('state', 'not in', ['draft', 'cancel'])]) ('state', 'not in', ['draft', 'cancel'])])
if plan_ids: if plan_ids:
sum_qty = sum([p.product_qty for p in plan_ids]) sum_qty = sum([p.product_qty for p in plan_ids])
if sum_qty >= self.default_capacity: date_planned_working_hours = self._compute_effective_working_hours_day1(date_planned)
default_capacity = round(
self.production_line_hour_capacity * date_planned_working_hours, 2)
_logger.info('排程日期:%s,计划数量:%s,日产能:%s,日工时:%s' % (
date_planned, sum_qty, default_capacity, date_planned_working_hours))
if sum_qty >= default_capacity:
return False return False
return True return True

View File

@@ -1082,6 +1082,12 @@ class ResMrpWorkOrder(models.Model):
# 重写工单开始按钮方法 # 重写工单开始按钮方法
def button_start(self): def button_start(self):
if self.routing_type == 'CNC加工':
self.env['sf.production.plan'].sudo().search([('name', '=', self.production_id.name)]).write({
'state': 'processing',
'actual_start_time': datetime.now()
})
if self.routing_type == '装夹预调': if self.routing_type == '装夹预调':
# 判断是否有坯料的序列号信息 # 判断是否有坯料的序列号信息
boolean = False boolean = False
@@ -1582,8 +1588,11 @@ class SfWorkOrderBarcodes(models.Model):
[('routing_type', '=', '装夹预调'), ('rfid_code', '=', barcode)]) [('routing_type', '=', '装夹预调'), ('rfid_code', '=', barcode)])
if workorder_olds: if workorder_olds:
name = '' name = ''
tem_list = []
for workorder in workorder_olds: for workorder in workorder_olds:
name = '%s %s' % (name, workorder.production_id.name) tem_list.append(workorder.production_id.name)
for i in list(set(tem_list)):
name = '%s %s' % (name, i)
raise UserError('该托盘已绑定【%s】制造订单,请先解除绑定!!!' % name) raise UserError('该托盘已绑定【%s】制造订单,请先解除绑定!!!' % name)
if workorder: if workorder:
if workorder.routing_type == '装夹预调': if workorder.routing_type == '装夹预调':
@@ -1817,6 +1826,11 @@ class WorkPieceDelivery(models.Model):
return is_free return is_free
else: else:
raise UserError("接驳站暂未反馈站点实时状态,请稍后再试") raise UserError("接驳站暂未反馈站点实时状态,请稍后再试")
def delivery_avg(self):
is_agv_task_dispatch = self.env['ir.config_parameter'].sudo().get_param('is_agv_task_dispatch')
if is_agv_task_dispatch:
self._delivery_avg()
# 配送至avg小车 # 配送至avg小车
def _delivery_avg(self): def _delivery_avg(self):
@@ -1877,7 +1891,7 @@ class WorkPieceDelivery(models.Model):
logging.info('delivery_item-name:%s' % delivery_item.name) logging.info('delivery_item-name:%s' % delivery_item.name)
delivery_item.write({ delivery_item.write({
'task_delivery_time': fields.Datetime.now(), 'task_delivery_time': fields.Datetime.now(),
'status': '待配送' 'status': '已下发'
}) })
if delivery_item.type == "上产线": if delivery_item.type == "上产线":
delivery_item.workorder_id.write({'is_delivery': True}) delivery_item.workorder_id.write({'is_delivery': True})

View File

@@ -44,6 +44,9 @@ class ResProductMo(models.Model):
materials_id = fields.Many2one('sf.production.materials', string='材料') materials_id = fields.Many2one('sf.production.materials', string='材料')
materials_type_id = fields.Many2one('sf.materials.model', string='材料型号', materials_type_id = fields.Many2one('sf.materials.model', string='材料型号',
domain="[('materials_id', '=', materials_id)]") domain="[('materials_id', '=', materials_id)]")
# materials_type_id = fields.Many2one(related='cutting_tool_model_id.material_model_id', string='材料型号',
# domain="[('materials_id', '=', materials_id)]")
# cutting_tool_model_id.material_model_id
server_product_process_parameters_id = fields.Many2one('sf.production.process.parameter', server_product_process_parameters_id = fields.Many2one('sf.production.process.parameter',
string='表面工艺参数(服务产品)') string='表面工艺参数(服务产品)')
model_process_parameters_ids = fields.Many2many('sf.production.process.parameter', 'process_parameter_rel', model_process_parameters_ids = fields.Many2many('sf.production.process.parameter', 'process_parameter_rel',
@@ -57,40 +60,100 @@ class ResProductMo(models.Model):
cutting_tool_type_id = fields.Many2one('sf.cutting.tool.type', string='类型', cutting_tool_type_id = fields.Many2one('sf.cutting.tool.type', string='类型',
domain="[('cutting_tool_material_id.name', '=', cutting_tool_type)]") domain="[('cutting_tool_material_id.name', '=', cutting_tool_type)]")
# cutting_tool_type_id = fields.Many2one(related='cutting_tool_model_id.cutting_tool_type_id', string='类型',
brand_id = fields.Many2one('sf.machine.brand', '品牌') # domain="[('cutting_tool_material_id.name', '=', cutting_tool_type)]")
# brand_id = fields.Many2one('sf.machine.brand', '品牌')
tool_length = fields.Float('长度(mm)') brand_id = fields.Many2one(related='cutting_tool_model_id.brand_id', string='品牌')
tool_width = fields.Float('宽度(mm)') # cutting_tool_model_id.brand_id
# tool_length = fields.Float('长度(mm)')
tool_length = fields.Float(related='specification_id.length', string='长度(mm)')
# specification_id.length
# tool_width = fields.Float('宽度(mm)')
tool_width = fields.Float(related='specification_id.width', string='宽度(mm)')
# specification_id.width
tool_height = fields.Float('高度(mm)') tool_height = fields.Float('高度(mm)')
tool_thickness = fields.Float('厚度(mm)') # tool_thickness = fields.Float('厚度(mm)')
tool_weight = fields.Float('重量(kg)') tool_thickness = fields.Float(related='specification_id.thickness', string='厚度(mm)')
tool_hardness = fields.Integer('硬度(hrc)') # specification_id.thickness
coating_material = fields.Char('涂层材质') # tool_weight = fields.Float('重量(kg)')
tool_weight = fields.Float(related='specification_id.weight', string='重量(kg)')
# specification_id.weight
# tool_hardness = fields.Integer('硬度(hrc)')
tool_hardness = fields.Integer(related='cutting_tool_model_id.tool_hardness', string='硬度(hrc)')
# cutting_tool_model_id.tool_hardness
# coating_material = fields.Char('涂层材质')
coating_material = fields.Char(related='cutting_tool_model_id.coating_material', string='涂层材质')
# cutting_tool_model_id.coating_material
# 整体式刀具特有字段 # 整体式刀具特有字段
cutting_tool_total_length = fields.Float('总长度(mm)', digits=(6, 1)) # cutting_tool_total_length = fields.Float('总长度(mm)', digits=(6, 1))
cutting_tool_shank_length = fields.Float('柄部长度(mm)', digits=(6, 1)) cutting_tool_total_length = fields.Float(related='specification_id.total_length', string='长度(mm)', digits=(6, 1))
cutting_tool_blade_length = fields.Float('刃部长度(mm)') # specification_id.total_length
cutting_tool_blade_number = fields.Selection( # cutting_tool_shank_length = fields.Float('柄部长度(mm)', digits=(6, 1))
[('0', '0'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ('6', '6'), ('7', '7'), ('8', '8')], cutting_tool_shank_length = fields.Float(related='specification_id.handle_length', string='柄部长度(mm)',
string='刃数(个)', default='0') digits=(6, 1))
# specification_id.handle_length
# cutting_tool_blade_length = fields.Float('刃部长度(mm)')
cutting_tool_blade_length = fields.Float(related='specification_id.blade_length', string='刃部长度(mm)')
# specification_id.blade_length
# cutting_tool_blade_number = fields.Selection(
# [('0', '0'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ('6', '6'), ('7', '7'), ('8', '8')],
# string='刃数(个)', default='0')
cutting_tool_blade_number = fields.Selection(related='specification_id.blade_number', string='刃数(个)')
# specification_id.blade_number
# 整体式刀具新增字段 # 整体式刀具新增字段
cutting_tool_neck_length = fields.Float('颈部长度(mm)', digits=(6, 1)) # cutting_tool_neck_length = fields.Float('颈部长度(mm)', digits=(6, 1))
cutting_tool_neck_diameter = fields.Float('颈部直径(mm)', digits=(6, 1)) cutting_tool_neck_length = fields.Float(related='specification_id.neck_length', string='颈部长度(mm)', digits=(6, 1))
cutting_tool_shank_diameter = fields.Float('柄部直径(mm)', digits=(6, 1)) # specification_id.neck_length
cutting_tool_blade_tip_diameter = fields.Float('刀尖直径(mm)', digits=(6, 1)) # cutting_tool_neck_diameter = fields.Float('颈部直径(mm)', digits=(6, 1))
cutting_tool_blade_tip_taper = fields.Integer('刀尖锥度(°)') cutting_tool_neck_diameter = fields.Float(related='specification_id.neck_diameter', string='颈部直径(mm)',
cutting_tool_blade_helix_angle = fields.Integer('刃部螺旋角(°)') digits=(6, 1))
cutting_tool_blade_type = fields.Char('刃部类型') # specification_id.neck_diameter
cutting_tool_pitch = fields.Float('牙距(mm)') # cutting_tool_shank_diameter = fields.Float('柄部直径(mm)', digits=(6, 1))
cutting_tool_blade_width = fields.Float('刃部宽度(mm)') cutting_tool_shank_diameter = fields.Float(related='specification_id.handle_diameter', string='柄部直径(mm)', digits=(6, 1))
cutting_tool_blade_depth = fields.Float('刃部深度(mm)') # specification_id.handle_diameter
# cutting_tool_blade_tip_diameter = fields.Float('刀尖直径(mm)', digits=(6, 1))
cutting_tool_blade_tip_diameter = fields.Float(related='specification_id.blade_tip_diameter', string='刀尖直径(mm)',
digits=(6, 1))
# specification_id.blade_tip_diameter
# cutting_tool_blade_tip_taper = fields.Integer('刀尖锥度(°)')
cutting_tool_blade_tip_taper = fields.Integer(related='specification_id.blade_tip_taper', string='刀尖锥度(°)')
# specification_id.blade_tip_taper
# cutting_tool_blade_helix_angle = fields.Integer('刃部螺旋角(°)')
cutting_tool_blade_helix_angle = fields.Integer(related='specification_id.blade_helix_angle', string='刃部螺旋角(°)')
# specification_id.blade_helix_angle
# cutting_tool_blade_type = fields.Char('刃部类型')
cutting_tool_blade_type = fields.Char(related='cutting_tool_model_id.blade_type', string='刃部类型')
# cutting_tool_pitch = fields.Float('牙距(mm)')
cutting_tool_pitch = fields.Float(related='specification_id.pitch', string='牙距(mm)')
# specification_id.pitch
# cutting_tool_blade_width = fields.Float('刃部宽度(mm)')
cutting_tool_blade_width = fields.Float(related='specification_id.blade_width', string='刃部宽度(mm)')
# specification_id.blade_width
# cutting_tool_blade_depth = fields.Float('刃部深度(mm)')
cutting_tool_blade_depth = fields.Float(related='specification_id.blade_depth', string='刃部深度(mm)')
# specification_id.blade_depth
cutting_tool_cut_depth = fields.Float('切削深度(mm)') cutting_tool_cut_depth = fields.Float('切削深度(mm)')
cutting_tool_cut_depth_max = fields.Float('最大切削深度(mm)') # cutting_tool_cut_depth_max = fields.Float('最大切削深度(mm)')
cutting_tool_coarse_medium_fine = fields.Selection([('', ''), ('', ''), ('', '')], '粗/中/精') cutting_tool_cut_depth_max = fields.Float(related='specification_id.cut_depth_max', string='最大切削深度(mm)')
cutting_tool_run_out_accuracy_max = fields.Float('端跳精度max', digits=(6, 1)) # specification_id.cut_depth_max
cutting_tool_run_out_accuracy_min = fields.Float('端跳精度min', digits=(6, 1)) # cutting_tool_coarse_medium_fine = fields.Selection([('粗', '粗'), ('中', '中'), ('精', '精')], '粗/中/精')
cutting_tool_blade_tip_working_size = fields.Char('刀尖处理尺寸(R半径mm/倒角)', size=20) cutting_tool_coarse_medium_fine = fields.Selection(related='cutting_tool_model_id.integral_coarse_medium_fine', string='粗/中/精')
# cutting_tool_model_id.integral_coarse_medium_fine
# cutting_tool_run_out_accuracy_max = fields.Float('端跳精度max', digits=(6, 1))
cutting_tool_run_out_accuracy_max = fields.Char(related='cutting_tool_model_id.integral_run_out_accuracy_max', string='端跳精度max', digits=(6, 1))
# cutting_tool_model_id.integral_run_out_accuracy_max
# cutting_tool_run_out_accuracy_min = fields.Float('端跳精度min', digits=(6, 1))
cutting_tool_run_out_accuracy_min = fields.Char(related='cutting_tool_model_id.integral_run_out_accuracy_min',
string='端跳精度min', digits=(6, 1))
# cutting_tool_model_id.integral_run_out_accuracy_min
# cutting_tool_blade_tip_working_size = fields.Char('刀尖倒角度(°)', size=20)
cutting_tool_blade_tip_working_size = fields.Char(related='specification_id.blade_tip_working_size',
string='刀尖倒角度(°)', size=20)
# specification_id.blade_tip_working_size
# cutting_tool_blade_tip_r_size = fields.Float('刀尖R角(mm)')
cutting_tool_blade_tip_r_size = fields.Float(related='specification_id.tip_r_size',
string='刀尖R角(mm)')
# specification_id.tip_r_size
fit_blade_shape_id = fields.Many2one('maintenance.equipment.image', fit_blade_shape_id = fields.Many2one('maintenance.equipment.image',
'适配刀片形状', domain=[('type', '=', '刀片形状')]) '适配刀片形状', domain=[('type', '=', '刀片形状')])
suitable_machining_method_ids = fields.Many2many('maintenance.equipment.image', suitable_machining_method_ids = fields.Many2many('maintenance.equipment.image',
@@ -237,6 +300,7 @@ class ResProductMo(models.Model):
self.cutting_tool_blade_tip_taper = self.specification_id.blade_tip_taper self.cutting_tool_blade_tip_taper = self.specification_id.blade_tip_taper
self.cutting_tool_blade_helix_angle = self.specification_id.blade_helix_angle self.cutting_tool_blade_helix_angle = self.specification_id.blade_helix_angle
self.cutting_tool_blade_tip_working_size = self.specification_id.blade_tip_working_size self.cutting_tool_blade_tip_working_size = self.specification_id.blade_tip_working_size
self.cutting_tool_blade_tip_r_size = self.specification_id.tip_r_size
self.cutting_tool_pitch = self.specification_id.pitch self.cutting_tool_pitch = self.specification_id.pitch
self.cutting_tool_blade_width = self.specification_id.blade_width self.cutting_tool_blade_width = self.specification_id.blade_width
self.cutting_tool_blade_depth = self.specification_id.blade_depth self.cutting_tool_blade_depth = self.specification_id.blade_depth
@@ -427,44 +491,97 @@ class ResProductMo(models.Model):
# if not self.cutting_direction_ids: # if not self.cutting_direction_ids:
# raise ValidationError("请选择走刀方向") # raise ValidationError("请选择走刀方向")
cutting_speed_ids = fields.One2many('sf.cutting.speed', 'product_template_id', string='切削速度Vc') # cutting_speed_ids = fields.One2many('sf.cutting.speed', 'product_template_id', string='切削速度Vc')
cutting_speed_ids = fields.One2many(related='cutting_tool_model_id.cutting_speed_ids',
string='切削速度Vc')
# cutting_tool_model_id.cutting_speed_ids
feed_per_tooth_ids = fields.One2many('sf.feed.per.tooth', 'product_template_id', string='每齿走刀量fz') feed_per_tooth_ids = fields.One2many('sf.feed.per.tooth', 'product_template_id', string='每齿走刀量fz')
cutting_tool_diameter = fields.Float('刀具直径(mm)') cutting_tool_diameter = fields.Float('刀具直径(mm)')
cutting_tool_rear_angle = fields.Integer('后角(°)') # cutting_tool_rear_angle = fields.Integer('后角(°)')
cutting_tool_main_included_angle = fields.Integer('主偏角(°)') cutting_tool_rear_angle = fields.Integer(related='specification_id.relief_angle',
string='后角(°)')
# specification_id.relief_angle
# cutting_tool_main_included_angle = fields.Integer('主偏角(°)')
cutting_tool_main_included_angle = fields.Integer(related='specification_id.main_included_angle',
string='主偏角(°)')
# specification_id.main_included_angle
# 适用夹头型号可以多选 # 适用夹头型号可以多选
cutting_tool_chuck_id = fields.Many2one( cutting_tool_chuck_id = fields.Many2one(
'sf.cutting_tool.standard.library', 'sf.cutting_tool.standard.library',
domain="[('cutting_tool_type', '=', '夹头')]", domain="[('cutting_tool_type', '=', '夹头')]",
string='适用夹头型号') string='适用夹头型号')
# 刀片参数 # 刀片参数
cutting_tool_cut_blade_length = fields.Float('切削刃长(mm)') # cutting_tool_cut_blade_length = fields.Float('切削刃长(mm)')
cutting_tool_blade_tip_circular_arc_radius = fields.Char('刀尖圆弧半径(mm)', size=20) cutting_tool_cut_blade_length = fields.Float(related='specification_id.cutting_blade_length',
cutting_tool_top_angle = fields.Integer('顶角(°)') string='切削刃长(mm)')
cutting_tool_inscribed_circle_diameter = fields.Float('内接圆直径(mm)') # specification_id.cutting_blade_length
cutting_tool_install_aperture_diameter = fields.Float('安装孔直径(mm)') # cutting_tool_blade_tip_circular_arc_radius = fields.Char('刀尖圆弧半径(mm)', size=20)
cutting_tool_chip_breaker_groove = fields.Selection([('', ''), ('单面', '单面'), ('双面', '双面')], cutting_tool_blade_tip_circular_arc_radius = fields.Char(related='specification_id.blade_tip_circular_arc_radius',
string='有无断屑槽') string='刀尖圆弧半径(mm)')
cutting_tool_chip_breaker_type_code = fields.Char('断屑槽型代号') # specification_id.blade_tip_circular_arc_radius
cutting_tool_bladed_teeth_model = fields.Selection( # cutting_tool_top_angle = fields.Integer('顶角(°)')
[('', ''), ('V牙型', 'V牙型'), ('米制全牙型', '米制全牙型'), ('美制全牙型', '美制全牙型'), cutting_tool_top_angle = fields.Integer(related='specification_id.top_angle',
('惠氏全牙型', '惠氏全牙型'), ('BSPT全牙型', 'BSPT全牙型'), ('NPT全牙型', 'NPT全牙型'), string='顶角(°)')
('UNJ全牙型', 'UNJ全牙型'), ('DIN405圆牙型', 'DIN405圆牙型'), ('ACME梯形', 'ACME梯形'), # specification_id.top_angle
('石油管螺纹刀片', '石油管螺纹刀片'), ('矮牙ACME梯形', '矮牙ACME梯形'), # cutting_tool_inscribed_circle_diameter = fields.Float('内接圆直径(mm)')
('Trapeze30° 103', 'Trapeze30° 103')], string='刀片牙型', default='') cutting_tool_inscribed_circle_diameter = fields.Float(related='specification_id.inscribed_circle_diameter',
cutting_tool_blade_blade_number = fields.Selection( string='内接圆直径(mm)')
[('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ('6', '6'), # specification_id.inscribed_circle_diameter
('7', '7'), ('8', '8'), ('9', '9'), ('10', '10')], # cutting_tool_install_aperture_diameter = fields.Float('安装孔直径(mm)')
string='刀片的刃数(个)') cutting_tool_install_aperture_diameter = fields.Float(related='specification_id.install_aperture_diameter',
string='安装孔直径(mm)')
# specification_id.install_aperture_diameter
# cutting_tool_chip_breaker_groove = fields.Selection([('无', '无'), ('单面', '单面'), ('双面', '双面')],
# string='有无断屑槽')
cutting_tool_chip_breaker_groove = fields.Selection(related='specification_id.chip_breaker_groove',
string='有无断屑槽')
# specification_id.chip_breaker_groove
# cutting_tool_chip_breaker_type_code = fields.Char('断屑槽型代号')
cutting_tool_chip_breaker_type_code = fields.Char(related='specification_id.chip_breaker_type_code',
string='断屑槽型代号')
# specification_id.chip_breaker_type_code
# cutting_tool_bladed_teeth_model = fields.Selection(
# [('无', '无'), ('V牙型', 'V牙型'), ('米制全牙型', '米制全牙型'), ('美制全牙型', '美制全牙型'),
# ('惠氏全牙型', '惠氏全牙型'), ('BSPT全牙型', 'BSPT全牙型'), ('NPT全牙型', 'NPT全牙型'),
# ('UNJ全牙型', 'UNJ全牙型'), ('DIN405圆牙型', 'DIN405圆牙型'), ('ACME梯形', 'ACME梯形'),
# ('石油管螺纹刀片', '石油管螺纹刀片'), ('矮牙ACME梯形', '矮牙ACME梯形'),
# ('Trapeze30° 103', 'Trapeze30° 103')], string='刀片牙型', default='无')
cutting_tool_bladed_teeth_model = fields.Selection(related='specification_id.blade_teeth_model',
string='断屑槽型代号')
# specification_id.blade_teeth_model
# cutting_tool_blade_blade_number = fields.Selection(
# [('0', '0'), ('1', '1'), ('2', '2'), ('3', '3'), ('4', '4'), ('5', '5'), ('6', '6'),
# ('7', '7'), ('8', '8'), ('9', '9'), ('10', '10')],
# string='刀片的刃数(个)')
cutting_tool_blade_blade_number = fields.Selection(related='specification_id.blade_blade_number',
string='刀片的刃数(个)')
# specification_id.blade_blade_number
cutting_tool_thread_model = fields.Selection([('', ''), ('外螺纹', '外螺纹'), ('内螺纹', '内螺纹')], # cutting_tool_thread_model = fields.Selection([('无', '无'), ('外螺纹', '外螺纹'), ('内螺纹', '内螺纹')],
string='螺纹类型') # string='螺纹类型')
cutting_tool_thread_num = fields.Float('每英寸螺纹数(tpi)') cutting_tool_thread_model = fields.Selection(related='specification_id.thread_model',
cutting_tool_blade_tip_height_tolerance = fields.Char('刀尖高度公差(mm)', size=20) string='螺纹类型')
cutting_tool_inscribed_circle_tolerance = fields.Char('内接圆公差(mm)', size=20) # specification_id.thread_model
cutting_tool_thickness_tolerance = fields.Char('厚度公差(mm)', size=20) # cutting_tool_thread_num = fields.Float('每英寸螺纹数(tpi)')
cutting_tool_thread_num = fields.Float(related='specification_id.thread_num',
cutting_tool_jump_accuracy = fields.Char('径跳精度(mm)') string='每英寸螺纹数(tpi)')
# specification_id.thread_num
# cutting_tool_blade_tip_height_tolerance = fields.Char('刀尖高度公差(mm)', size=20)
cutting_tool_blade_tip_height_tolerance = fields.Char(related='specification_id.blade_tip_height_tolerance',
string='刀尖高度公差(mm)')
# specification_id.blade_tip_height_tolerance
# cutting_tool_inscribed_circle_tolerance = fields.Char('内接圆公差(mm)', size=20)
cutting_tool_inscribed_circle_tolerance = fields.Char(related='specification_id.inscribed_circle_tolerance',
string='内接圆公差(mm)')
# specification_id.inscribed_circle_tolerance
# cutting_tool_thickness_tolerance = fields.Char('厚度公差(mm)', size=20)
cutting_tool_thickness_tolerance = fields.Char(related='specification_id.thickness_tolerance',
string='厚度公差(mm)')
# specification_id.thickness_tolerance
# cutting_tool_jump_accuracy = fields.Char('径跳精度(mm)')
cutting_tool_jump_accuracy = fields.Char(related='specification_id.run_out_accuracy',
string='径跳精度(mm)')
# specification_id.run_out_accuracy
cutting_tool_working_hardness = fields.Integer('加工硬度(hrc)') cutting_tool_working_hardness = fields.Integer('加工硬度(hrc)')
cutting_tool_cutter_bar_ids = fields.Many2many( cutting_tool_cutter_bar_ids = fields.Many2many(
'sf.cutting_tool.standard.library', 'sf.cutting_tool.standard.library',
@@ -483,55 +600,153 @@ class ResProductMo(models.Model):
string='适用刀盘型号' # 使用空列表作为默认值 string='适用刀盘型号' # 使用空列表作为默认值
) )
# 刀杆/参数 # 刀杆/参数
cutting_tool_knife_head_height = fields.Float('刀头高度(mm)') # cutting_tool_knife_head_height = fields.Float('刀头高度(mm)')
cutting_tool_knife_head_width = fields.Float('刀头宽度(mm)') cutting_tool_knife_head_height = fields.Float(related='specification_id.knife_head_height',
cutting_tool_knife_head_length = fields.Float('刀头度(mm)') string='刀头度(mm)')
cutting_tool_blade_diameter = fields.Float('刃径/刃部直径(mm)') # specification_id.knife_head_height
cutting_tool_cutter_arbor_diameter = fields.Float('刀杆直径(mm)') # cutting_tool_knife_head_width = fields.Float('刀头宽度(mm)')
cutting_tool_min_machining_aperture = fields.Integer('最小加工孔径(mm)') cutting_tool_knife_head_width = fields.Float(related='specification_id.knife_head_width',
cutting_tool_install_blade_tip_num = fields.Integer('可装刀片数/齿数(个)') string='刀头宽度(mm)')
cutting_tool_installing_structure = fields.Char('安装结构', size=20) # cutting_tool_knife_head_length = fields.Float('刀头长度(mm)')
cutting_tool_knife_head_length = fields.Float(related='specification_id.knife_head_length',
string='刀头长度(mm)')
# cutting_tool_blade_diameter = fields.Float('刃径/刃部直径(mm)')
cutting_tool_blade_diameter = fields.Float(related='specification_id.blade_diameter',
string='刃径/刃部直径(mm)')
# specification_id.blade_diameter
# cutting_tool_cutter_arbor_diameter = fields.Float('刀杆直径(mm)')
cutting_tool_cutter_arbor_diameter = fields.Float(related='specification_id.cutter_arbor_diameter',
string='刀杆直径(mm)')
# specification_id.cutter_arbor_diameter
# cutting_tool_min_machining_aperture = fields.Integer('最小加工孔径(mm)')
cutting_tool_min_machining_aperture = fields.Integer(related='specification_id.min_machining_aperture',
string='最小加工孔径(mm)')
# specification_id.min_machining_aperture
# cutting_tool_install_blade_tip_num = fields.Integer('可装刀片数/齿数(个)')
cutting_tool_install_blade_tip_num = fields.Integer(related='specification_id.install_blade_tip_num',
string='可装刀片数/齿数(个)')
# specification_id.install_blade_tip_num
# cutting_tool_installing_structure = fields.Char('安装结构', size=20)
cutting_tool_installing_structure = fields.Char(related='specification_id.installing_structure',
string='安装结构')
# specification_id.installing_structure
cutting_tool_blade_id = fields.Many2one( cutting_tool_blade_id = fields.Many2one(
'sf.cutting_tool.standard.library', 'sf.cutting_tool.standard.library',
domain="[('cutting_tool_type', '=', '刀片')]", domain="[('cutting_tool_type', '=', '刀片')]",
string='适用刀片型号' # 使用空列表作为默认值 string='适用刀片型号' # 使用空列表作为默认值
) )
cutting_tool_tool_shim = fields.Char('适配刀垫型号', size=50) # cutting_tool_blade_id = fields.Many2one(related='specification_id.blade_id',
cutting_tool_cotter_pin = fields.Char('适配销钉型号', size=50) # domain="[('cutting_tool_type', '=', '刀片')]",
cutting_tool_pressing_plate = fields.Char('适配压板型号', size=50) # string='适用刀片型号')
cutting_tool_screw = fields.Char('适配螺钉型号', size=50) # specification_id.blade_id
cutting_tool_wrench = fields.Char('适配扳手型号') # cutting_tool_tool_shim = fields.Char('适配刀垫型号', size=50)
cutting_tool_is_cooling_hole = fields.Boolean('有无冷却孔', default=False) cutting_tool_tool_shim = fields.Char(related='specification_id.tool_shim',
cutting_tool_locating_slot_code = fields.Char('定位槽代号', size=20) string='适配刀垫型号')
# specification_id.tool_shim
# cutting_tool_cotter_pin = fields.Char('适配销钉型号', size=50)
cutting_tool_cotter_pin = fields.Char(related='specification_id.cotter_pin',
string='适配销钉型号')
# cutting_tool_pressing_plate = fields.Char('适配压板型号', size=50)
cutting_tool_pressing_plate = fields.Char(related='specification_id.pressing_plate',
string='适配压板型号')
# cutting_tool_screw = fields.Char('适配螺钉型号', size=50)
cutting_tool_screw = fields.Char(related='specification_id.screw',
string='适配螺钉型号')
# specification_id.screw
# cutting_tool_wrench = fields.Char('适配扳手型号')
cutting_tool_wrench = fields.Char(related='specification_id.spanner',
string='适配扳手型号')
# specification_id.spanner
# cutting_tool_is_cooling_hole = fields.Boolean('有无冷却孔', default=False)
cutting_tool_is_cooling_hole = fields.Boolean(related='specification_id.is_cooling_hole',
string='有无冷却孔')
# specification_id.is_cooling_hole
# cutting_tool_locating_slot_code = fields.Char('定位槽代号', size=20)
cutting_tool_locating_slot_code = fields.Char(related='specification_id.locating_slot_code',
string='定位槽代号')
# specification_id.locating_slot_code
# 刀盘参数 # 刀盘参数
cutting_tool_cutter_head_diameter = fields.Float('刀盘直径(mm)') # cutting_tool_cutter_head_diameter = fields.Float('刀盘直径(mm)')
cutting_tool_interface_diameter = fields.Float('接口直径(mm)') cutting_tool_cutter_head_diameter = fields.Float(related='specification_id.cutter_head_diameter',
string='刀盘直径(mm)')
# specification_id.cutter_head_diameter
# cutting_tool_interface_diameter = fields.Float('接口直径(mm)')
cutting_tool_interface_diameter = fields.Float(related='specification_id.interface_diameter',
string='接口直径(mm)')
# specification_id.interface_diameter
# 刀柄参数 # 刀柄参数
cutting_tool_clamping_diameter_max = fields.Float('最大夹持直径') # cutting_tool_clamping_diameter_max = fields.Float('最大夹持直径')
cutting_tool_clamping_diameter_min = fields.Float('最小夹持直径') cutting_tool_clamping_diameter_max = fields.Float(related='specification_id.max_clamping_diameter',
cutting_tool_flange_length = fields.Float('法兰柄长(mm)') string='最大夹持直径')
cutting_tool_flange_diameter = fields.Float('法兰直径(mm)') # specification_id.max_clamping_diameter
cutting_tool_is_safety_lock = fields.Boolean('有无安全锁', default=False) # cutting_tool_clamping_diameter_min = fields.Float('最小夹持直径')
cutting_tool_is_high_speed_cutting = fields.Boolean('可高速切削', default=False) cutting_tool_clamping_diameter_min = fields.Float(related='specification_id.min_clamping_diameter',
cutting_tool_change_time = fields.Integer('换刀时间(s)') string='最小夹持直径')
cutting_tool_clamping_way = fields.Char('夹持方式') # specification_id.min_clamping_diameter
cutting_tool_fit_chuck_size = fields.Char('适配夹头尺寸') # cutting_tool_flange_length = fields.Float('法兰柄长(mm)')
cutting_tool_taper_shank_model = fields.Char('锥柄型号') cutting_tool_flange_length = fields.Float(related='specification_id.flange_shank_length',
string='法兰柄长(mm)')
# cutting_tool_flange_diameter = fields.Float('法兰直径(mm)')
cutting_tool_flange_diameter = fields.Float(related='specification_id.flange_diameter',
string='法兰直径(mm)')
# cutting_tool_is_safety_lock = fields.Boolean('有无安全锁', default=False)
cutting_tool_is_safety_lock = fields.Boolean(related='specification_id.is_safe_lock',
string='有无安全锁')
# cutting_tool_is_high_speed_cutting = fields.Boolean('可高速切削', default=False)
cutting_tool_is_high_speed_cutting = fields.Boolean(related='specification_id.is_quick_cutting',
string='可高速切削')
# cutting_tool_change_time = fields.Integer('换刀时间(s)')
cutting_tool_change_time = fields.Integer(related='specification_id.tool_changing_time',
string='换刀时间(s)')
# cutting_tool_clamping_way = fields.Char('夹持方式')
cutting_tool_clamping_way = fields.Char(related='specification_id.clamping_mode',
string='夹持方式')
# cutting_tool_fit_chuck_size = fields.Char('适配夹头尺寸')
cutting_tool_fit_chuck_size = fields.Char(related='specification_id.fit_chuck_size',
string='适配夹头尺寸')
# cutting_tool_taper_shank_model = fields.Char('锥柄型号')
cutting_tool_taper_shank_model = fields.Char(related='specification_id.taper_shank_model',
string='锥柄型号')
cutting_tool_standard_speed = fields.Integer('标准转速(n/min)') cutting_tool_standard_speed = fields.Integer('标准转速(n/min)')
cutting_tool_speed_max = fields.Integer('最大转速(n/min)') # cutting_tool_speed_max = fields.Integer('最大转速(n/min)')
cutting_tool_speed_max = fields.Integer(related='specification_id.max_rotate_speed',
string='最大转速(n/min)')
# specification_id.max_rotate_speed
cutting_tool_cooling_type = fields.Char('冷却类型') cutting_tool_cooling_type = fields.Char('冷却类型')
cutting_tool_dynamic_balance_class = fields.Char('动平衡等级') cutting_tool_dynamic_balance_class = fields.Char('动平衡等级')
cutting_tool_fit_nut_model = fields.Char('适用锁紧螺母型号') # cutting_tool_fit_nut_model = fields.Char('适用锁紧螺母型号')
cutting_tool_fit_nut_model = fields.Char(related='specification_id.nut',
string='适用锁紧螺母型号')
# specification_id.nut
# 夹头参数 # 夹头参数
cutting_tool_taper = fields.Integer('锥度(°)') # cutting_tool_taper = fields.Integer('锥度(°)')
cutting_tool_top_diameter = fields.Float('顶部直径') cutting_tool_taper = fields.Integer(related='specification_id.taper',
cutting_tool_outer_diameter = fields.Float('外径(mm)') string='锥度(°)')
cutting_tool_inner_diameter = fields.Float('内径(mm)') # specification_id.taper
cooling_suit_type_ids = fields.Char('适用冷却套型号') # cutting_tool_top_diameter = fields.Float('顶部直径')
cutting_tool_max_load_capacity = fields.Float('最大负载能力(kg)') cutting_tool_top_diameter = fields.Float(related='specification_id.top_diameter',
cutting_tool_er_size_model = fields.Char('尺寸型号') string='顶部直径')
# specification_id.top_diameter
# cutting_tool_outer_diameter = fields.Float('外径(mm)')
# specification_id.outer_diameter
cutting_tool_outer_diameter = fields.Float(related='specification_id.outer_diameter',
string='外径(mm)')
# cutting_tool_inner_diameter = fields.Float('内径(mm)')
cutting_tool_inner_diameter = fields.Float(related='specification_id.inner_diameter',
string='内径(mm)')
# specification_id.inner_diameter
# cooling_suit_type_ids = fields.Char('适用冷却套型号')
cooling_suit_type_ids = fields.Char(related='specification_id.cooling_jacket',
string='适用冷却套型号')
# specification_id.cooling_jacket
# cutting_tool_max_load_capacity = fields.Float('最大负载能力(kg)')
cutting_tool_max_load_capacity = fields.Float(related='specification_id.max_load_capacity',
string='最大负载能力(kg)')
# specification_id.max_load_capacity
# cutting_tool_er_size_model = fields.Char('尺寸型号')
cutting_tool_er_size_model = fields.Char(related='specification_id.er_size_model',
string='尺寸型号')
# specification_id.er_size_model
# cutting_tool_handle_ids = fields.Many2many( # cutting_tool_handle_ids = fields.Many2many(
# 'sf.cutting_tool.standard.library', # 'sf.cutting_tool.standard.library',
# relation='product_cutting_tool_library_chuck_handle_rel', # relation='product_cutting_tool_library_chuck_handle_rel',
@@ -546,6 +761,10 @@ class ResProductMo(models.Model):
domain="[('cutting_tool_type', '=', '刀柄')]", domain="[('cutting_tool_type', '=', '刀柄')]",
string='适用刀柄型号' string='适用刀柄型号'
) )
# cutting_tool_handle_id = fields.Many2one(related='cutting_tool_model_id.handle_id',
# domain="[('cutting_tool_type', '=', '刀柄')]",
# string='适用刀柄型号')
# cutting_tool_model_id.handle_id
# 注册状态 # 注册状态
register_state = fields.Selection([('未注册', '未注册'), ('已注册', '已注册'), ('注册失败', '注册失败')], register_state = fields.Selection([('未注册', '未注册'), ('已注册', '已注册'), ('注册失败', '注册失败')],

View File

@@ -296,6 +296,10 @@
</attribute> </attribute>
</xpath> </xpath>
<xpath expr="//sheet//notebook//page[@name='operations']//field[@name='workorder_ids']" position="replace">
<field name="workorder_ids" attrs="{'readonly': ['|', ('state', '!=', 'test_value'), '&amp;', ('state', '=', 'done'), ('is_locked', '=', True)]}" context="{'tree_view_ref': 'mrp.mrp_production_workorder_tree_editable_view', 'default_product_uom_id': product_uom_id, 'from_manufacturing_order': True}"/>
</xpath>
<xpath expr="//sheet//notebook//page[@name='operations']" position="after"> <xpath expr="//sheet//notebook//page[@name='operations']" position="after">
<page string="检测结果" attrs="{'invisible': [('detection_result_ids', '=', [])]}"> <page string="检测结果" attrs="{'invisible': [('detection_result_ids', '=', [])]}">
<field name="detection_result_ids" string="" readonly="0"> <field name="detection_result_ids" string="" readonly="0">

View File

@@ -520,7 +520,7 @@
<xpath expr="//form//header" position="inside"> <xpath expr="//form//header" position="inside">
<button type="object" class="oe_highlight jikimo_button_confirm" name="get_three_check_datas" <button type="object" class="oe_highlight jikimo_button_confirm" name="get_three_check_datas"
string="获取数据" attrs='{"invisible": [("state","!=","progress")]}'/> string="获取数据" attrs='{"invisible": ["|", ("state","!=","progress"), ("routing_type","!=","装夹预调")]}'/>
</xpath> </xpath>
@@ -797,7 +797,7 @@
<field name="feeder_station_start_id" readonly="1" force_save="1"/> <field name="feeder_station_start_id" readonly="1" force_save="1"/>
<!-- <field name="type" readonly="1"/>--> <!-- <field name="type" readonly="1"/>-->
<field name="feeder_station_destination_id" readonly="1" force_save="1"/> <field name="feeder_station_destination_id" readonly="1" force_save="1"/>
<button name="button_delivery" type="object" string="配送" class="oe_highlight"/> <button name="delivery_avg" type="object" string="配送" class="oe_highlight"/>
<button name="action_delivery_history" type="object" class="btn btn-link text-info" icon="fa-history" <button name="action_delivery_history" type="object" class="btn btn-link text-info" icon="fa-history"
string="历史"/> string="历史"/>
</tree> </tree>

View File

@@ -1 +1,2 @@
from . import models from . import models
from . import controllers

View File

@@ -11,10 +11,12 @@
""", """,
'category': 'sf', 'category': 'sf',
'website': 'https://www.sf.jikimo.com', 'website': 'https://www.sf.jikimo.com',
'depends': ['base', 'sf_plan', 'sf_sale'], 'depends': ['sale', 'purchase', 'sf_plan', 'jikimo_message_notify', 'stock'],
'data': [ 'data': [
'security/ir.model.access.csv', 'data/bussiness_node.xml',
'views/sf_message_template_view.xml', # 'data/cron_data.xml',
'data/template_data.xml',
], ],
'test': [ 'test': [
], ],

View File

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

View File

@@ -0,0 +1,40 @@
import json
import requests
import logging
from odoo import http
from odoo.http import request
from odoo.addons.sf_mrs_connect.controllers.controllers import Sf_Mrs_Connect
from odoo.addons.sf_base.commons.common import Common
_logger = logging.getLogger(__name__)
class MessageSfMrsConnect(Sf_Mrs_Connect):
@http.route('/api/cnc_processing/create', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*")
def get_cnc_processing_create(self, **kw):
res = super(MessageSfMrsConnect, self).get_cnc_processing_create(**kw)
res = json.loads(res)
if res.get('production_ids'):
try:
_logger.info('已编程的制造订单:%s' % res.get('production_ids'))
productions = request.env['mrp.production'].sudo().search([('id', 'in', res.get('production_ids'))])
# 过滤programming_state为已编程,tool_state为2的制造订单
tool_state_valid_productions = productions.filtered(lambda x: x.programming_state == '已编程' and x.tool_state == '2')
if tool_state_valid_productions:
data = {
'name': tool_state_valid_productions[0].programming_no
}
# 请求cloud接口发送微信消息推送
configsettings = request.env['res.config.settings'].sudo().get_values()
config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key'])
url = '/api/message/invalid_tool_state'
config_url = configsettings['sf_url'] + url
data['token'] = configsettings['token']
ret = requests.post(config_url, json=data, headers=config_header)
ret = ret.json()
_logger.info('无效用刀异常消息推送接口:%s' % ret)
except Exception as e:
_logger.info('无效用刀异常消息推送接口:%s' % e)
return json.JSONEncoder().encode(res)

View File

@@ -0,0 +1,89 @@
<?xml version="1.0" ?>
<odoo>
<data noupdate="1">
<!--销售订单-->
<record id="bussiness_pending_order" model="jikimo.message.bussiness.node">
<field name="name">待接单</field>
<field name="model">sale.order</field>
</record>
<record id="bussiness_to_be_confirm" model="jikimo.message.bussiness.node">
<field name="name">确认接单</field>
<field name="model">sale.order</field>
</record>
<!-- <record id="bussiness_sale_order_overdue_warning" model="jikimo.message.bussiness.node">-->
<!-- <field name="name">销售订单逾期预警</field>-->
<!-- <field name="model">sale.order</field>-->
<!-- </record>-->
<!-- <record id="bussiness_sale_order_overdue" model="jikimo.message.bussiness.node">-->
<!-- <field name="name">销售订单已逾期</field>-->
<!-- <field name="model">sale.order</field>-->
<!-- </record>-->
<record id="transfer_inventory" model="jikimo.message.bussiness.node">
<field name="name">调拨入库</field>
<field name="model">stock.picking</field>
</record>
<record id="tool_assembly" model="jikimo.message.bussiness.node">
<field name="name">功能刀具组装</field>
<field name="model">sf.functional.tool.assembly</field>
</record>
<record id="tool_dismantle" model="jikimo.message.bussiness.node">
<field name="name">功能刀具寿命到期</field>
<field name="model">sf.functional.tool.dismantle</field>
</record>
<record id="bussiness_material_purchase_remind" model="jikimo.message.bussiness.node">
<field name="name">坯料采购提醒</field>
<field name="model">purchase.order</field>
</record>
<record id="bussiness_material_picking_remind" model="jikimo.message.bussiness.node">
<field name="name">坯料发料提醒</field>
<field name="model">stock.picking</field>
</record>
<!--工单-->
<record id="bussiness_mrp_workorder_remind" model="jikimo.message.bussiness.node">
<field name="name">工单已下发通知</field>
<field name="model">mrp.workorder</field>
</record>
<!-- <record id="bussiness_mrp_workorder_pre_overdue_warning" model="jikimo.message.bussiness.node">-->
<!-- <field name="name">装夹预调工单逾期预警</field>-->
<!-- <field name="model">mrp.workorder</field>-->
<!-- </record>-->
<!-- <record id="bussiness_mrp_workorder_pre_overdue" model="jikimo.message.bussiness.node">-->
<!-- <field name="name">装夹预调工单已逾期</field>-->
<!-- <field name="model">mrp.workorder</field>-->
<!-- </record>-->
<!-- <record id="bussiness_mrp_workorder_cnc_overdue_warning" model="jikimo.message.bussiness.node">-->
<!-- <field name="name">CNC工单逾期预警</field>-->
<!-- <field name="model">mrp.workorder</field>-->
<!-- </record>-->
<!-- <record id="bussiness_mrp_workorder_cnc_overdue" model="jikimo.message.bussiness.node">-->
<!-- <field name="name">CNC工单已逾期</field>-->
<!-- <field name="model">mrp.workorder</field>-->
<!-- </record>-->
<!-- <record id="bussiness_mrp_workorder_unclamp_overdue_warning" model="jikimo.message.bussiness.node">-->
<!-- <field name="name">解除装夹工单逾期预警</field>-->
<!-- <field name="model">mrp.workorder</field>-->
<!-- </record>-->
<!-- <record id="bussiness_mrp_workorder_unclamp_overdue" model="jikimo.message.bussiness.node">-->
<!-- <field name="name">解除装夹工单已逾期</field>-->
<!-- <field name="model">mrp.workorder</field>-->
<!-- </record>-->
<!-- <record id="bussiness_mrp_workorder_surface_overdue_warning" model="jikimo.message.bussiness.node">-->
<!-- <field name="name">表面工艺工单逾期预警</field>-->
<!-- <field name="model">mrp.workorder</field>-->
<!-- </record>-->
<!-- <record id="bussiness_mrp_workorder_surface_overdue" model="jikimo.message.bussiness.node">-->
<!-- <field name="name">表面工艺工单已逾期</field>-->
<!-- <field name="model">mrp.workorder</field>-->
<!-- </record>-->
</data>
</odoo>

View File

@@ -0,0 +1,160 @@
<odoo>
<data noupdate="1">
<record model="ir.cron" id="ir_cron_sale_order_overdue_warning">
<field name="name">销售订单逾期预警</field>
<field name="model_id" ref="model_sale_order"/>
<field name="state">code</field>
<field name="code">model._overdue_warning_func()</field>
<field name="interval_number">1</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="user_id" ref="base.user_root"/>
<field name="active" eval="True"/>
</record>
<record model="ir.cron" id="ir_cron_sale_order_overdue">
<field name="name">销售订单已逾期</field>
<field name="model_id" ref="model_sale_order"/>
<field name="state">code</field>
<field name="code">model._overdue_func()</field>
<field name="interval_number">1</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="user_id" ref="base.user_root"/>
<field name="active" eval="True"/>
</record>
<record model="ir.cron" id="ir_cron_mrp_workorder_overdue_warning">
<field name="name">装夹预调工单逾期预警</field>
<field name="model_id" ref="model_mrp_workorder"/>
<field name="state">code</field>
<field name="code">model._overdue_warning_func()</field>
<field name="interval_number">1</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="user_id" ref="base.user_root"/>
<field name="active" eval="True"/>
</record>
<record model="ir.cron" id="ir_cron_mrp_workorder_overdue">
<field name="name">工单已逾期</field>
<field name="model_id" ref="model_mrp_workorder"/>
<field name="state">code</field>
<field name="code">model._overdue_func()</field>
<field name="interval_number">1</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="user_id" ref="base.user_root"/>
<field name="active" eval="True"/>
</record>
<!-- -->
<!-- <record model="ir.cron" id="ir_cron_mrp_workorder_overdue_warning">-->
<!-- <field name="name">工单逾期预警</field>-->
<!-- <field name="model_id" ref="model_mrp_workorder"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">model._overdue_warning_func()</field>-->
<!-- <field name="interval_number">1</field>-->
<!-- <field name="interval_type">minutes</field>-->
<!-- <field name="numbercall">-1</field>-->
<!-- <field name="doall" eval="False"/>-->
<!-- <field name="user_id" ref="base.user_root"/>-->
<!-- <field name="active" eval="True"/>-->
<!-- </record>-->
<!-- <record model="ir.cron" id="ir_cron_mrp_workorder_pre_overdue">-->
<!-- <field name="name">工单已逾期</field>-->
<!-- <field name="model_id" ref="model_mrp_workorder"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">model._overdue_func()</field>-->
<!-- <field name="interval_number">1</field>-->
<!-- <field name="interval_type">minutes</field>-->
<!-- <field name="numbercall">-1</field>-->
<!-- <field name="doall" eval="False"/>-->
<!-- <field name="user_id" ref="base.user_root"/>-->
<!-- <field name="active" eval="True"/>-->
<!-- </record>-->
<!-- -->
<!-- <record model="ir.cron" id="ir_cron_mrp_workorder_pre_overdue_warning">-->
<!-- <field name="name">工单逾期预警</field>-->
<!-- <field name="model_id" ref="model_mrp_workorder"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">model._overdue_warning_func()</field>-->
<!-- <field name="interval_number">1</field>-->
<!-- <field name="interval_type">minutes</field>-->
<!-- <field name="numbercall">-1</field>-->
<!-- <field name="doall" eval="False"/>-->
<!-- <field name="user_id" ref="base.user_root"/>-->
<!-- <field name="active" eval="True"/>-->
<!-- </record>-->
<!-- <record model="ir.cron" id="ir_cron_mrp_workorder_pre_overdue">-->
<!-- <field name="name">工单已逾期</field>-->
<!-- <field name="model_id" ref="model_mrp_workorder"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">model._overdue_func()</field>-->
<!-- <field name="interval_number">1</field>-->
<!-- <field name="interval_type">minutes</field>-->
<!-- <field name="numbercall">-1</field>-->
<!-- <field name="doall" eval="False"/>-->
<!-- <field name="user_id" ref="base.user_root"/>-->
<!-- <field name="active" eval="True"/>-->
<!-- </record>-->
<!-- -->
<!-- -->
<!-- <record model="ir.cron" id="ir_cron_mrp_workorder_pre_overdue_warning">-->
<!-- <field name="name">工单逾期预警</field>-->
<!-- <field name="model_id" ref="model_mrp_workorder"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">model._overdue_warning_func()</field>-->
<!-- <field name="interval_number">1</field>-->
<!-- <field name="interval_type">minutes</field>-->
<!-- <field name="numbercall">-1</field>-->
<!-- <field name="doall" eval="False"/>-->
<!-- <field name="user_id" ref="base.user_root"/>-->
<!-- <field name="active" eval="True"/>-->
<!-- </record>-->
<!-- <record model="ir.cron" id="ir_cron_mrp_workorder_pre_overdue">-->
<!-- <field name="name">工单已逾期</field>-->
<!-- <field name="model_id" ref="model_mrp_workorder"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">model._overdue_func()</field>-->
<!-- <field name="interval_number">1</field>-->
<!-- <field name="interval_type">minutes</field>-->
<!-- <field name="numbercall">-1</field>-->
<!-- <field name="doall" eval="False"/>-->
<!-- <field name="user_id" ref="base.user_root"/>-->
<!-- <field name="active" eval="True"/>-->
<!-- </record>-->
<!-- -->
<!-- <record model="ir.cron" id="ir_cron_mrp_workorder_pre_overdue_warning">-->
<!-- <field name="name">工单逾期预警</field>-->
<!-- <field name="model_id" ref="model_mrp_workorder"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">model._overdue_warning_func()</field>-->
<!-- <field name="interval_number">1</field>-->
<!-- <field name="interval_type">minutes</field>-->
<!-- <field name="numbercall">-1</field>-->
<!-- <field name="doall" eval="False"/>-->
<!-- <field name="user_id" ref="base.user_root"/>-->
<!-- <field name="active" eval="True"/>-->
<!-- </record>-->
<!-- <record model="ir.cron" id="ir_cron_mrp_workorder_pre_overdue">-->
<!-- <field name="name">工单已逾期</field>-->
<!-- <field name="model_id" ref="model_mrp_workorder"/>-->
<!-- <field name="state">code</field>-->
<!-- <field name="code">model._overdue_func()</field>-->
<!-- <field name="interval_number">1</field>-->
<!-- <field name="interval_type">minutes</field>-->
<!-- <field name="numbercall">-1</field>-->
<!-- <field name="doall" eval="False"/>-->
<!-- <field name="user_id" ref="base.user_root"/>-->
<!-- <field name="active" eval="True"/>-->
<!-- </record>-->
</data>
</odoo>

View File

@@ -0,0 +1,110 @@
<?xml version="1.0" ?>
<odoo>
<data noupdate="1">
<record id="template_pending_order" model="jikimo.message.template">
<field name="name">待接单</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="model">sale.order</field>
<field name="bussiness_node_id" ref="bussiness_pending_order"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 待接单提醒:
单号:销售订单[{{name}}]({{url}})
事项:请确认是否接单。
</field>
</record>
<record id="template_to_be_confirm" model="jikimo.message.template">
<field name="name">确认接单</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="model">sale.order</field>
<field name="bussiness_node_id" ref="bussiness_to_be_confirm"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 待排程提醒:
单号:产品[{{product_id}}]({{url}})
事项:{{mrp_production_count}}个制造订单待计划排程
</field>
</record>
<record id="template_material_purchase_remind" model="jikimo.message.template">
<field name="name">坯料采购提醒</field>
<field name="model_id" ref="purchase.model_purchase_order"/>
<field name="model">purchase.order</field>
<field name="bussiness_node_id" ref="bussiness_material_purchase_remind"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 坯料采购通知:
单号:采购单[{{name}}]({{request_url}})
事项:请确认坯料采购单并处理</field>
</record>
<record id="template_material_picking_remind" model="jikimo.message.template">
<field name="name">坯料发料提醒</field>
<field name="model_id" ref="stock.model_stock_picking"/>
<field name="model">stock.picking</field>
<field name="bussiness_node_id" ref="bussiness_material_picking_remind"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 坯料发料提醒:
单号:产品[{{product_id}}]({{request_url}})
事项:共{{number}}个生产发料单待确认处理</field>
</record>
<record id="template_mrp_workorder_remind" model="jikimo.message.template">
<field name="name">工单已下发通知</field>
<field name="model_id" ref="mrp_workorder.model_mrp_workorder"/>
<field name="model">mrp.workorder</field>
<field name="bussiness_node_id" ref="bussiness_mrp_workorder_remind"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 工单已下发通知:
单号:产品[{{product_id}}]({{request_url}})
事项:共{{number}}个工单已下发,请查收知悉</field>
</record>
<record id="template_transfer_inventory_remind" model="jikimo.message.template">
<field name="menu_id" ref="stock.menu_stock_root"/>
<field name="action_id" ref="stock.action_picking_tree_ready"/>
<field name="name">调拨入库</field>
<field name="model_id" ref="stock.model_stock_picking"/>
<field name="model">stock.picking</field>
<field name="bussiness_node_id" ref="transfer_inventory"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 调拨入库通知:
单号:调拨入库单[{{name}}]({{request_url}})
事项:完成刀具物料上架入库</field>
</record>
<record id="template_tool_expired_remind" model="jikimo.message.template">
<field name="menu_id" ref="mrp.menu_mrp_root"/>
<field name="action_id" ref="sf_tool_management.sf_functional_tool_dismantle_view_act"/>
<field name="name">功能刀具寿命到期</field>
<field name="model_id" ref="sf_tool_management.model_sf_functional_tool_dismantle"/>
<field name="model">sf.functional.tool.dismantle</field>
<field name="bussiness_node_id" ref="tool_dismantle"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 功能刀具寿命到期提醒:
单号:拆解单[{{code}}]({{request_url}})
事项:{{functional_tool_id.tool_name_id.name}}寿命已到期,需拆解</field>
</record>
<record id="template_tool_assembly_remind" model="jikimo.message.template">
<field name="menu_id" ref="mrp.menu_mrp_root"/>
<field name="action_id" ref="sf_tool_management.sf_functional_tool_assembly_view_act"/>
<field name="name">功能刀具组装</field>
<field name="model_id" ref="sf_tool_management.model_sf_functional_tool_assembly"/>
<field name="model">sf.functional.tool.assembly</field>
<field name="bussiness_node_id" ref="tool_assembly"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 功能刀具组装通知:
单号:组装任务单[{{name}}]({{request_url}})
事项:{{use_tool_time}}前完成组装</field>
</record>
</data>
</odoo>

View File

@@ -6,3 +6,4 @@ from . import sf_message_cam_program
from . import sf_message_functional_tool_assembly from . import sf_message_functional_tool_assembly
from . import sf_message_purchase from . import sf_message_purchase
from . import sf_message_workorder from . import sf_message_workorder
from . import sf_message_functional_tool_dismantle

View File

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

View File

@@ -3,4 +3,13 @@ from odoo import models, fields, api, _
class SFMessagefunctionalToolAssembly(models.Model): class SFMessagefunctionalToolAssembly(models.Model):
_name = 'sf.functional.tool.assembly' _name = 'sf.functional.tool.assembly'
_inherit = ['sf.functional.tool.assembly', 'sf.message.template'] _description = "刀具组装单"
_inherit = ['sf.functional.tool.assembly', 'jikimo.message.dispatch']
@api.model_create_multi
def create(self, vals):
result = super(SFMessagefunctionalToolAssembly, self).create(vals)
for obj in result:
if obj.loading_task_source == '0' and obj.assemble_status == '0':
obj.add_queue('功能刀具组装')
return result

View File

@@ -0,0 +1,19 @@
from odoo import models, api
class SFMessagefunctionalToolDismantle(models.Model):
_name = 'sf.functional.tool.dismantle'
_description = "刀具拆解单"
_inherit = ['sf.functional.tool.dismantle', 'jikimo.message.dispatch']
@api.model
def create(self, vals):
# 判断是否为web页面创建请求
is_web_request = self.env.context.get('is_web_request', False)
result = super(SFMessagefunctionalToolDismantle, self).create(vals)
if is_web_request:
return result
for obj in result:
if obj.dismantle_cause in ['寿命到期报废', '崩刀报废'] and obj.state == '待拆解':
obj.add_queue('功能刀具寿命到期')
return result

View File

@@ -1,6 +1,25 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _ from odoo import models, fields, api, _
class SFMessagePlan(models.Model): class SFMessagePlan(models.Model):
_name = 'sf.production.plan' _name = 'sf.production.plan'
_inherit = ['sf.production.plan', 'sf.message.template'] _inherit = ['sf.production.plan', 'jikimo.message.dispatch']
# def create(self, vals_list):
# res = super(SFMessagePlan, self).create(vals_list)
# if res:
# try:
# res.add_queue('待排程')
# except Exception as e:
# logging.info('add_queue error:%s' % e)
# return res
#
# def _get_message(self):
# res = super(SFMessagePlan, self)._get_message()
# if res:
# try:
# res.add_queue('待排程')
# except Exception as e:
# logging.info('_get_message error:%s' % e)
# return res

View File

@@ -1,6 +1,33 @@
from odoo import models, fields, api, _ from odoo import models, fields, api, _
from urllib.parse import urlencode
class SFMessagePurchase(models.Model): class SFMessagePurchase(models.Model):
_name = 'purchase.order' _name = 'purchase.order'
_inherit = ['purchase.order', 'sf.message.template'] _inherit = ['purchase.order', 'jikimo.message.dispatch']
def _get_message(self, message_queue_ids):
contents = []
for message_queue_id in message_queue_ids:
if message_queue_id.message_template_id.name == '坯料采购提醒':
content = message_queue_id.message_template_id.content
url = self.request_url(int(message_queue_id.res_id))
purchase_order_line = self.env['purchase.order'].search([('id', '=', int(message_queue_id.res_id))])
content = content.replace('{{name}}', purchase_order_line.name).replace(
'{{request_url}}', url)
contents.append(content)
return contents
def request_url(self, id):
url = self.env['ir.config_parameter'].get_param('web.base.url')
action_id = self.env.ref('purchase.purchase_form_action').id
menu_id = self.env['ir.model.data'].search([('name', '=', 'module_website_payment')]).id
# 查询参数
params = {'id': id, 'menu_id': menu_id, 'action': action_id,
'model': 'purchase.order',
'view_type': 'form'}
# 拼接查询参数
query_string = urlencode(params)
# 拼接URL
full_url = url + "/web#" + query_string
return full_url

View File

@@ -1,11 +1,74 @@
# -*- coding: utf-8 -*-
import logging
from odoo import models, fields, api, _ from odoo import models, fields, api, _
class SFMessageSale(models.Model): class SFMessageSale(models.Model):
_name = 'sale.order' _name = 'sale.order'
_inherit = ['sale.order', 'sf.message.template'] _inherit = ['sale.order', 'jikimo.message.dispatch']
# def create(self): @api.model_create_multi
# res = super(SFMessageSale, self).create() def create(self, vals_list):
# if res is True: res = super(SFMessageSale, self).create(vals_list)
if res:
try:
res.add_queue('待接单')
except Exception as e:
logging.info('add_queue error:%s' % e)
return res
# 确认接单
def action_confirm(self):
res = super(SFMessageSale, self).action_confirm()
if res is True:
try:
self.add_queue('确认接单')
picking_ids = self.mrp_production_ids
purchase_order_id = []
if picking_ids:
for picking_id in picking_ids:
purchase_order_ids = (
picking_id.procurement_group_id.stock_move_ids.created_purchase_line_id.order_id |
picking_id.procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id).ids
purchase_order_id.extend(purchase_order_ids)
if purchase_order_id:
purchase_order_list = self.env['purchase.order'].search([('id', 'in', purchase_order_id)])
for purchase_order_info in purchase_order_list:
purchase_order_info.add_queue('坯料采购提醒')
except Exception as e:
logging.info('add_queue error:%s' % e)
return res
# 继承并重写jikimo.message.dispatch的_get_message()
def _get_message(self, message_queue_ids):
contents = []
url = self.env['ir.config_parameter'].get_param('web.base.url')
for item in message_queue_ids:
# 待接单的处理
if item.message_template_id.bussiness_node_id.name == '待接单':
content = super(SFMessageSale, self)._get_message(item)
action_id = self.env.ref('sale.action_quotations_with_onboarding').id
url = f"{url}/web#id={item.res_id}&view_type=form&action={action_id}"
content = content[0].replace('{{url}}', url)
contents.append(content)
# 确认接单的处理
elif item.message_template_id.bussiness_node_id.name == '确认接单':
content = super(SFMessageSale, self)._get_message(item)
sale_order_line = self.env['sale.order.line'].search([('order_id', '=', int(item.res_id))])
product = sale_order_line[0].product_id.name if len(sale_order_line) == 1 else '%s...' % \
sale_order_line[
0].product_id.name
action_id = self.env.ref('sf_plan.sf_production_plan_action1').id
url = f"{url}/web#view_type=list&action={action_id}"
content = content[0].replace('{{product_id}}', product).replace('{{url}}', url)
contents.append(content)
return contents
# # 销售订单逾期预警
# def _overdue_warning_func(self):
# sale_order_
# return 1
#
# # 销售订单已逾期
# def _overdue_func(self):
# return 1

View File

@@ -1,6 +1,65 @@
import re
from odoo import models, fields, api, _ from odoo import models, fields, api, _
from urllib.parse import urlencode
class SFMessageStockPicking(models.Model): class SFMessageStockPicking(models.Model):
_name = 'stock.picking' _name = 'stock.picking'
_inherit = ['stock.picking', 'sf.message.template'] _description = "库存调拨"
_inherit = ['stock.picking', 'jikimo.message.dispatch']
@api.model_create_multi
def create(self, vals):
result = super(SFMessageStockPicking, self).create(vals)
for obj in result:
if obj.location_id.name == '进货' and obj.location_dest_id.name == '刀具房':
obj.add_queue('调拨入库')
return result
@api.depends('move_type', 'immediate_transfer', 'move_ids.state', 'move_ids.picking_id')
def _compute_state(self):
super(SFMessageStockPicking, self)._compute_state()
for record in self:
if record.state == 'assigned' and record.check_in == 'PC':
record.add_queue('坯料发料提醒')
def _get_message(self, message_queue_ids):
contents = []
product_id = []
for message_queue_id in message_queue_ids:
i = 0
if message_queue_id.message_template_id.name == '坯料发料提醒':
content = message_queue_id.message_template_id.content
stock_picking_line = self.env['stock.picking'].search([('id', '=', int(message_queue_id.res_id))])
mrp_production_info = self.env['mrp.production'].search(
[('name', '=', stock_picking_line.origin)])
mrp_production_list = self.env['mrp.production'].search(
[('product_id', '=', mrp_production_info.product_id.id)])
for mrp_production_line in mrp_production_list:
picking_ids = mrp_production_line.picking_ids
for picking_id in picking_ids:
if picking_id.state == 'assigned' and picking_id.check_in == 'PC':
i += 1
if i > 0 and mrp_production_info.product_id.id not in product_id:
url = self.request_url()
content = content.replace('{{product_id}}', mrp_production_info.product_id.name).replace(
'{{number}}', str(i)).replace('{{request_url}}', url)
product_id.append(mrp_production_info.product_id.id)
contents.append(content)
return contents
else:
res = super(SFMessageStockPicking, self)._get_message(message_queue_id)
return res
def request_url(self):
url = self.env['ir.config_parameter'].get_param('web.base.url')
action_id = self.env.ref('stock.stock_picking_type_action').id
menu_id = self.env['ir.model.data'].search([('name', '=', 'module_theme_treehouse')]).id
# 查询参数
params = {'menu_id': menu_id, 'action': action_id, 'model': 'stock.picking',
'view_type': 'kanban'}
# 拼接查询参数
query_string = urlencode(params)
# 拼接URL
full_url = url + "/web#" + query_string
return full_url

View File

@@ -4,31 +4,14 @@ from abc import ABC, abstractmethod
class SfMessageTemplate(models.Model): class SfMessageTemplate(models.Model):
_name = "sf.message.template" _inherit = "jikimo.message.template"
_description = u'消息模板'
name = fields.Char(string=u"名称", required=True) def _get_message_model(self):
description = fields.Char(string=u"描述") res = super(SfMessageTemplate, self)._get_message_model()
content = fields.Html(string=u"内容", render_engine='qweb', translate=True, prefetch=True, sanitize=False) res.append("sale.order")
msgtype = fields.Selection( res.append("stock.picking")
[('text', u'文字'), ('markdown', u'Markdown')], u'消息类型', res.append('sf.functional.tool.assembly')
required=True, default='markdown') res.append('sf.functional.tool.dismantle')
notification_department_id = fields.Many2one('hr.department', u'通知部门', required=True) res.append('purchase.order')
notification_employee_ids = fields.Many2many('hr.employee', string=u'员工', res.append('mrp.workorder')
domain="[('department_id', '=',notification_department_id)]", return res
required=True)
is_send_time = fields.Boolean(string=u"定时发送", default=False)
send_time_1 = fields.Integer('发送时间点1')
send_time_2 = fields.Integer('发送时间点2')
active = fields.Boolean(string=u"是否有效", default=True)
@api.onchange('notification_department_id')
def _clear_employee_ids(self):
if self.notification_department_id:
self.notification_employee_ids = False
@abstractmethod
def dispatch(self, args):
"""
强迫继承该类必走该抽象方法'
"""

View File

@@ -1,6 +1,54 @@
from odoo import models, fields, api, _ from odoo import models, fields, api, _
import logging, json
import requests
from odoo.addons.sf_base.commons.common import Common
from urllib.parse import urlencode
_logger = logging.getLogger(__name__)
class SFMessageWork(models.Model): class SFMessageWork(models.Model):
_name = 'mrp.workorder' _name = 'mrp.workorder'
_inherit = ['mrp.workorder', 'sf.message.template'] _inherit = ['mrp.workorder', 'jikimo.message.dispatch']
@api.depends('production_availability', 'blocked_by_workorder_ids.state')
def _compute_state(self):
super(SFMessageWork, self)._compute_state()
for workorder in self:
if workorder.state == 'ready' and workorder.routing_type == '装夹预调':
jikimo_message_queue = self.env['jikimo.message.queue'].sudo().search(
[('res_id', '=', workorder.id), ("message_status", "=", "pending")])
if not jikimo_message_queue:
workorder.add_queue('工单已下发通知')
def _get_message(self, message_queue_ids):
contents = []
product_id = []
for message_queue_id in message_queue_ids:
if message_queue_id.message_template_id.name == '工单已下发通知':
content = message_queue_id.message_template_id.content
mrp_workorder_line = self.env['mrp.workorder'].search([('id', '=', int(message_queue_id.res_id))])
mrp_workorder_list = self.env['mrp.workorder'].search(
[('product_id', '=', mrp_workorder_line.product_id.id), ('state', '=', 'ready'),
('routing_type', '=', '装夹预调')])
if len(mrp_workorder_list) > 0 and mrp_workorder_line.product_id.id not in product_id:
url = self.request_url()
content = content.replace('{{product_id}}', mrp_workorder_line.product_id.name).replace(
'{{number}}', str(len(mrp_workorder_list))).replace(
'{{request_url}}', url)
product_id.append(mrp_workorder_line.product_id.id)
contents.append(content)
return contents
def request_url(self):
url = self.env['ir.config_parameter'].get_param('web.base.url')
action_id = self.env.ref('sf_manufacturing.mrp_workorder_action_tablet').id
menu_id = self.env['ir.model.data'].search([('name', '=', 'module_stock_dropshipping')]).id
# 查询参数
params = {'menu_id': menu_id, 'action': action_id, 'model': 'mrp.workorder',
'view_type': 'list', 'active_id': 1}
# 拼接查询参数
query_string = urlencode(params)
# 拼接URL
full_url = url + "/web#" + query_string
return full_url

View File

@@ -1,9 +1,23 @@
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_sf_message_template_group_sale_salemanager,sf_message_template,model_sf_message_template,sf_base.group_sale_salemanager,1,1,1,0
access_sf_message_template_group_purchase,sf_message_template,model_sf_message_template,sf_base.group_purchase,1,1,1,0 access_jikimo_message_template_group_sale_salemanager,jikimo_message_template,model_jikimo_message_template,sf_base.group_sale_salemanager,1,1,1,0
access_sf_message_template_group_sf_stock_user,sf_message_template,model_sf_message_template,sf_base.group_sf_stock_user,1,1,1,0 access_jikimo_message_template_group_purchase,jikimo_message_template,model_jikimo_message_template,sf_base.group_purchase,1,1,1,0
access_sf_message_template_group_sf_order_user,sf_message_template,model_sf_message_template,sf_base.group_sf_order_user,1,1,1,0 access_jikimo_message_template_group_sf_stock_user,jikimo_message_template,model_jikimo_message_template,sf_base.group_sf_stock_user,1,1,1,0
access_sf_message_template_group_sf_tool_user,sf_message_template,model_sf_message_template,sf_base.group_sf_tool_user,1,1,1,0 access_jikimo_message_template_group_sf_order_user,jikimo_message_template,model_jikimo_message_template,sf_base.group_sf_order_user,1,1,1,0
access_jikimo_message_template_group_sf_tool_user,jikimo_message_template,model_jikimo_message_template,sf_base.group_sf_tool_user,1,1,1,0
access_jikimo_message_bussiness_node_group_sale_salemanager,jikimo_message_bussiness_node,model_jikimo_message_bussiness_node,sf_base.group_sale_salemanager,1,1,1,0
access_jikimo_message_bussiness_node_group_purchase,jikimo_message_bussiness_node,model_jikimo_message_bussiness_node,sf_base.group_purchase,1,1,1,0
access_jikimo_message_bussiness_node_group_sf_stock_user,jikimo_message_bussiness_node,model_jikimo_message_bussiness_node,sf_base.group_sf_stock_user,1,1,1,0
access_jikimo_message_bussiness_node_group_sf_order_user,jikimo_message_bussiness_node,model_jikimo_message_bussiness_node,sf_base.group_sf_order_user,1,1,1,0
access_jikimo_message_bussiness_node_group_sf_tool_user,jikimo_message_bussiness_node,model_jikimo_message_bussiness_node,sf_base.group_sf_tool_user,1,1,1,0
access_jikimo_message_queue_group_sale_salemanager,jikimo_message_queue,model_jikimo_message_queue,sf_base.group_sale_salemanager,1,1,1,0
access_jikimo_message_queue_group_purchase,jikimo_message_queue,model_jikimo_message_queue,sf_base.group_purchase,1,1,1,0
access_jikimo_message_queue_group_sf_stock_user,jikimo_message_queue,model_jikimo_message_queue,sf_base.group_sf_stock_user,1,1,1,0
access_jikimo_message_queue_group_sf_order_user,jikimo_message_queue,model_jikimo_message_queue,sf_base.group_sf_order_user,1,1,1,0
access_jikimo_message_queue_group_sf_tool_user,jikimo_message_queue,model_jikimo_message_queue,sf_base.group_sf_tool_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sf_message_template_group_sale_salemanager access_jikimo_message_template_group_sale_salemanager sf_message_template jikimo_message_template model_sf_message_template model_jikimo_message_template sf_base.group_sale_salemanager 1 1 1 0
3 access_sf_message_template_group_purchase access_jikimo_message_template_group_purchase sf_message_template jikimo_message_template model_sf_message_template model_jikimo_message_template sf_base.group_purchase 1 1 1 0
4 access_sf_message_template_group_sf_stock_user access_jikimo_message_template_group_sf_stock_user sf_message_template jikimo_message_template model_sf_message_template model_jikimo_message_template sf_base.group_sf_stock_user 1 1 1 0
5 access_sf_message_template_group_sf_order_user access_jikimo_message_template_group_sf_order_user sf_message_template jikimo_message_template model_sf_message_template model_jikimo_message_template sf_base.group_sf_order_user 1 1 1 0
6 access_sf_message_template_group_sf_tool_user access_jikimo_message_template_group_sf_tool_user sf_message_template jikimo_message_template model_sf_message_template model_jikimo_message_template sf_base.group_sf_tool_user 1 1 1 0
7 access_jikimo_message_bussiness_node_group_sale_salemanager jikimo_message_bussiness_node model_jikimo_message_bussiness_node sf_base.group_sale_salemanager 1 1 1 0
8 access_jikimo_message_bussiness_node_group_purchase jikimo_message_bussiness_node model_jikimo_message_bussiness_node sf_base.group_purchase 1 1 1 0
9 access_jikimo_message_bussiness_node_group_sf_stock_user jikimo_message_bussiness_node model_jikimo_message_bussiness_node sf_base.group_sf_stock_user 1 1 1 0
10 access_jikimo_message_bussiness_node_group_sf_order_user jikimo_message_bussiness_node model_jikimo_message_bussiness_node sf_base.group_sf_order_user 1 1 1 0
11 access_jikimo_message_bussiness_node_group_sf_tool_user jikimo_message_bussiness_node model_jikimo_message_bussiness_node sf_base.group_sf_tool_user 1 1 1 0
12 access_jikimo_message_queue_group_sale_salemanager jikimo_message_queue model_jikimo_message_queue sf_base.group_sale_salemanager 1 1 1 0
13 access_jikimo_message_queue_group_purchase jikimo_message_queue model_jikimo_message_queue sf_base.group_purchase 1 1 1 0
14 access_jikimo_message_queue_group_sf_stock_user jikimo_message_queue model_jikimo_message_queue sf_base.group_sf_stock_user 1 1 1 0
15 access_jikimo_message_queue_group_sf_order_user jikimo_message_queue model_jikimo_message_queue sf_base.group_sf_order_user 1 1 1 0
16 access_jikimo_message_queue_group_sf_tool_user jikimo_message_queue model_jikimo_message_queue sf_base.group_sf_tool_user 1 1 1 0
17
18
19
20
21
22
23

View File

@@ -7,7 +7,7 @@
<record id="sf_message_template_view_form" model="ir.ui.view"> <record id="sf_message_template_view_form" model="ir.ui.view">
<field name="name">sf.message.template.view.form</field> <field name="name">sf.message.template.view.form</field>
<field name="model">sf.message.template</field> <field name="model">message.template</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<form string="消息模板"> <form string="消息模板">
<sheet> <sheet>
@@ -18,14 +18,12 @@
</h1> </h1>
</div> </div>
<group> <group>
<!-- <field name="type"/>--> <!-- <field name="type"/>-->
<field name="notify_model_id"/>
<field name="content" widget="html" class="oe-bordered-editor" <field name="content" widget="html" class="oe-bordered-editor"
options="{'style-inline': true, 'codeview': true, 'dynamic_placeholder': true}"/> options="{'style-inline': true, 'codeview': true, 'dynamic_placeholder': true}"/>
<field name="description"/> <field name="description"/>
<field name="msgtype"/> <field name="msgtype"/>
<field name="is_send_time"/>
<field name="send_time_1" attrs="{'invisible': [('is_send_time', '=', False)]}"/>
<field name="send_time_2" attrs="{'invisible': [('is_send_time', '=', False)]}"/>
<field name="notification_department_id"/> <field name="notification_department_id"/>
<field name="notification_employee_ids" widget="many2many_tags"/> <field name="notification_employee_ids" widget="many2many_tags"/>
</group> </group>
@@ -36,16 +34,13 @@
<record id="sf_message_template_view_tree" model="ir.ui.view"> <record id="sf_message_template_view_tree" model="ir.ui.view">
<field name="name">sf.message.template.view.tree</field> <field name="name">sf.message.template.view.tree</field>
<field name="model">sf.message.template</field> <field name="model">message.template</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="消息模板"> <tree string="消息模板">
<field name="name"/> <field name="name"/>
<!-- <field name="type"/>--> <!-- <field name="type"/>-->
<field name="content"/> <field name="content"/>
<field name="msgtype"/> <field name="msgtype"/>
<field name="is_send_time"/>
<field name="send_time_1" attrs="{'invisible': [('is_send_time', '=', False)]}"/>
<field name="send_time_2" attrs="{'invisible': [('is_send_time', '=', False)]}"/>
<field name="notification_department_id"/> <field name="notification_department_id"/>
<field name="notification_employee_ids" widget="many2many_tags"/> <field name="notification_employee_ids" widget="many2many_tags"/>
<field name="description"/> <field name="description"/>
@@ -55,7 +50,7 @@
<record id="sf_message_template_search_view" model="ir.ui.view"> <record id="sf_message_template_search_view" model="ir.ui.view">
<field name="name">sf.message.template.search.view</field> <field name="name">sf.message.template.search.view</field>
<field name="model">sf.message.template</field> <field name="model">message.template</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search> <search>
<field name="name" string="模糊搜索" <field name="name" string="模糊搜索"
@@ -69,7 +64,7 @@
<!--定义单证类型视图动作--> <!--定义单证类型视图动作-->
<record id="sf_message_template_action" model="ir.actions.act_window"> <record id="sf_message_template_action" model="ir.actions.act_window">
<field name="name">消息模板</field> <field name="name">消息模板</field>
<field name="res_model">sf.message.template</field> <field name="res_model">message.template</field>
<field name="view_mode">tree,form</field> <field name="view_mode">tree,form</field>
<field name="view_id" ref="sf_message_template_view_tree"/> <field name="view_id" ref="sf_message_template_view_tree"/>
</record> </record>

View File

@@ -62,8 +62,8 @@ class Sf_Mrs_Connect(http.Controller):
if cnc_workorder_has.cnc_ids: if cnc_workorder_has.cnc_ids:
cnc_workorder_has.cmm_ids.sudo().unlink() cnc_workorder_has.cmm_ids.sudo().unlink()
cnc_workorder_has.cnc_ids.sudo().unlink() cnc_workorder_has.cnc_ids.sudo().unlink()
request.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan( # request.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan(
production) # production)
cnc_workorder_has.write( cnc_workorder_has.write(
{'cnc_ids': cnc_workorder_has.cnc_ids.sudo()._json_cnc_processing(panel, ret), {'cnc_ids': cnc_workorder_has.cnc_ids.sudo()._json_cnc_processing(panel, ret),
'cmm_ids': cnc_workorder_has.cmm_ids.sudo()._json_cmm_program(panel, ret)}) 'cmm_ids': cnc_workorder_has.cmm_ids.sudo()._json_cmm_program(panel, ret)})
@@ -93,6 +93,9 @@ class Sf_Mrs_Connect(http.Controller):
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())})
productions.write({'programming_state': '已编程', 'work_state': '已编程'}) productions.write({'programming_state': '已编程', 'work_state': '已编程'})
res.update({
'production_ids': productions.ids
})
return json.JSONEncoder().encode(res) return json.JSONEncoder().encode(res)
else: else:
res = {'status': 0, 'message': '该制造订单暂未开始'} res = {'status': 0, 'message': '该制造订单暂未开始'}

View File

@@ -18,7 +18,7 @@ class OrderPrice(models.Model):
return True return True
except ValueError: except ValueError:
return False return False
@api.depends('sale_order_id.remark') @api.depends('sale_order_id.order_line.remark')
def _compute_bfm_amount_total(self): def _compute_bfm_amount_total(self):
for record in self: for record in self:
amount_total = 0 amount_total = 0

View File

@@ -175,7 +175,7 @@ class ResConfigSettings(models.TransientModel):
new_price = res_order_lines_map.get(str(index)) new_price = res_order_lines_map.get(str(index))
if order_line: if order_line:
# 修改单价 # 修改单价
order_line.write({'remark': new_price*order_line.product_uom_qty}) order_line.write({'remark': round(new_price*order_line.product_uom_qty,2)})
order_price = self.env['order.price'].sudo().search([('sale_order_id', '=',need_change_sale_order.id )]) order_price = self.env['order.price'].sudo().search([('sale_order_id', '=',need_change_sale_order.id )])
if not order_price: if not order_price:
self.env['order.price'].sudo().create({'sale_order_id':need_change_sale_order.id}) self.env['order.price'].sudo().create({'sale_order_id':need_change_sale_order.id})

View File

@@ -2438,6 +2438,7 @@ class CuttingToolBasicParameters(models.Model):
'handle_length': integral_tool_item['shank_length'], 'handle_length': integral_tool_item['shank_length'],
'blade_tip_diameter': integral_tool_item['tip_diameter'], 'blade_tip_diameter': integral_tool_item['tip_diameter'],
'blade_tip_working_size': integral_tool_item['tip_handling_size'], 'blade_tip_working_size': integral_tool_item['tip_handling_size'],
'tip_r_size': integral_tool_item['tip_r_size'],
'blade_tip_taper': integral_tool_item['knife_tip_taper'], 'blade_tip_taper': integral_tool_item['knife_tip_taper'],
'blade_helix_angle': integral_tool_item['blade_helix_angle'], 'blade_helix_angle': integral_tool_item['blade_helix_angle'],
'blade_width': integral_tool_item['blade_width'], 'blade_width': integral_tool_item['blade_width'],
@@ -2459,6 +2460,7 @@ class CuttingToolBasicParameters(models.Model):
'handle_length': integral_tool_item['shank_length'], 'handle_length': integral_tool_item['shank_length'],
'blade_tip_diameter': integral_tool_item['tip_diameter'], 'blade_tip_diameter': integral_tool_item['tip_diameter'],
'blade_tip_working_size': integral_tool_item['tip_handling_size'], 'blade_tip_working_size': integral_tool_item['tip_handling_size'],
'tip_r_size': integral_tool_item['tip_r_size'],
'blade_tip_taper': integral_tool_item['knife_tip_taper'], 'blade_tip_taper': integral_tool_item['knife_tip_taper'],
'blade_helix_angle': integral_tool_item['blade_helix_angle'], 'blade_helix_angle': integral_tool_item['blade_helix_angle'],
'blade_width': integral_tool_item['blade_width'], 'blade_width': integral_tool_item['blade_width'],
@@ -2674,7 +2676,8 @@ class CuttingToolBasicParameters(models.Model):
'interface_diameter': cutter_head_item['interface_diameter'], 'interface_diameter': cutter_head_item['interface_diameter'],
'total_length': cutter_head_item['total_length'], 'total_length': cutter_head_item['total_length'],
'blade_length': cutter_head_item['blade_length'], 'blade_length': cutter_head_item['blade_length'],
'cutting_depth': cutter_head_item['cutting_depth_max'], 'cutting_blade_length': cutter_head_item['cutting_blade_length'],
'cut_depth_max': cutter_head_item['cutting_depth_max'],
'main_included_angle': cutter_head_item['edge_angle'], 'main_included_angle': cutter_head_item['edge_angle'],
'installing_structure': cutter_head_item['mounting_structure'], 'installing_structure': cutter_head_item['mounting_structure'],
'blade_id': False if not cutter_head_item['fit_blade_model_code'] else self.env[ 'blade_id': False if not cutter_head_item['fit_blade_model_code'] else self.env[
@@ -2696,7 +2699,8 @@ class CuttingToolBasicParameters(models.Model):
'interface_diameter': cutter_head_item['interface_diameter'], 'interface_diameter': cutter_head_item['interface_diameter'],
'total_length': cutter_head_item['total_length'], 'total_length': cutter_head_item['total_length'],
'blade_length': cutter_head_item['blade_length'], 'blade_length': cutter_head_item['blade_length'],
'cutting_depth': cutter_head_item['cutting_depth_max'], 'cutting_blade_length': cutter_head_item['cutting_blade_length'],
'cut_depth_max': cutter_head_item['cutting_depth_max'],
'main_included_angle': cutter_head_item['edge_angle'], 'main_included_angle': cutter_head_item['edge_angle'],
'installing_structure': cutter_head_item['mounting_structure'], 'installing_structure': cutter_head_item['mounting_structure'],
'blade_id': False if not cutter_head_item['fit_blade_model_code'] else self.env[ 'blade_id': False if not cutter_head_item['fit_blade_model_code'] else self.env[
@@ -2789,6 +2793,7 @@ class CuttingToolBasicParameters(models.Model):
'handle_length': integral_tool_item['shank_length'], 'handle_length': integral_tool_item['shank_length'],
'blade_tip_diameter': integral_tool_item['tip_diameter'], 'blade_tip_diameter': integral_tool_item['tip_diameter'],
'blade_tip_working_size': integral_tool_item['tip_handling_size'], 'blade_tip_working_size': integral_tool_item['tip_handling_size'],
'tip_r_size': integral_tool_item['tip_r_size'],
'blade_tip_taper': integral_tool_item['knife_tip_taper'], 'blade_tip_taper': integral_tool_item['knife_tip_taper'],
'blade_helix_angle': integral_tool_item['blade_helix_angle'], 'blade_helix_angle': integral_tool_item['blade_helix_angle'],
'blade_width': integral_tool_item['blade_width'], 'blade_width': integral_tool_item['blade_width'],
@@ -2810,6 +2815,7 @@ class CuttingToolBasicParameters(models.Model):
'handle_length': integral_tool_item['shank_length'], 'handle_length': integral_tool_item['shank_length'],
'blade_tip_diameter': integral_tool_item['tip_diameter'], 'blade_tip_diameter': integral_tool_item['tip_diameter'],
'blade_tip_working_size': integral_tool_item['tip_handling_size'], 'blade_tip_working_size': integral_tool_item['tip_handling_size'],
'tip_r_size': integral_tool_item['tip_r_size'],
'blade_tip_taper': integral_tool_item['knife_tip_taper'], 'blade_tip_taper': integral_tool_item['knife_tip_taper'],
'blade_helix_angle': integral_tool_item['blade_helix_angle'], 'blade_helix_angle': integral_tool_item['blade_helix_angle'],
'blade_width': integral_tool_item['blade_width'], 'blade_width': integral_tool_item['blade_width'],
@@ -3007,8 +3013,7 @@ class CuttingToolBasicParameters(models.Model):
}) })
if 'basic_parameters_cutter_head' in result['cutting_tool_basic_parameters_yesterday_list']: if 'basic_parameters_cutter_head' in result['cutting_tool_basic_parameters_yesterday_list']:
if result['cutting_tool_basic_parameters_yesterday_list']['basic_parameters_cutter_head']: if result['cutting_tool_basic_parameters_yesterday_list']['basic_parameters_cutter_head']:
basic_parameters_cutter_head_list = json.loads( basic_parameters_cutter_head_list = result['cutting_tool_basic_parameters_yesterday_list']['basic_parameters_cutter_head']
result['cutting_tool_basic_parameters_yesterday_list']['basic_parameters_cutter_head'])
if basic_parameters_cutter_head_list: if basic_parameters_cutter_head_list:
for cutter_head_item in basic_parameters_cutter_head_list: for cutter_head_item in basic_parameters_cutter_head_list:
cutter_head = self.search( cutter_head = self.search(
@@ -3027,7 +3032,8 @@ class CuttingToolBasicParameters(models.Model):
'interface_diameter': cutter_head_item['interface_diameter'], 'interface_diameter': cutter_head_item['interface_diameter'],
'total_length': cutter_head_item['total_length'], 'total_length': cutter_head_item['total_length'],
'blade_length': cutter_head_item['blade_length'], 'blade_length': cutter_head_item['blade_length'],
'cutting_depth': cutter_head_item['cutting_depth_max'], 'cutting_blade_length': cutter_head_item['cutting_blade_length'],
'cut_depth_max': cutter_head_item['cutting_depth_max'],
'main_included_angle': cutter_head_item['edge_angle'], 'main_included_angle': cutter_head_item['edge_angle'],
'installing_structure': cutter_head_item['mounting_structure'], 'installing_structure': cutter_head_item['mounting_structure'],
'blade_id': False if not cutter_head_item['fit_blade_model_code'] else self.env[ 'blade_id': False if not cutter_head_item['fit_blade_model_code'] else self.env[
@@ -3049,7 +3055,8 @@ class CuttingToolBasicParameters(models.Model):
'interface_diameter': cutter_head_item['interface_diameter'], 'interface_diameter': cutter_head_item['interface_diameter'],
'total_length': cutter_head_item['total_length'], 'total_length': cutter_head_item['total_length'],
'blade_length': cutter_head_item['blade_length'], 'blade_length': cutter_head_item['blade_length'],
'cutting_depth': cutter_head_item['cutting_depth_max'], 'cutting_blade_length': cutter_head_item['cutting_blade_length'],
'cut_depth_max': cutter_head_item['cutting_depth_max'],
'main_included_angle': cutter_head_item['edge_angle'], 'main_included_angle': cutter_head_item['edge_angle'],
'installing_structure': cutter_head_item['mounting_structure'], 'installing_structure': cutter_head_item['mounting_structure'],
'blade_id': False if not cutter_head_item['fit_blade_model_code'] else self.env[ 'blade_id': False if not cutter_head_item['fit_blade_model_code'] else self.env[

View File

@@ -1,7 +1,4 @@
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_sf_static_resource_datasync,sf_static_resource_datasync,model_sf_static_resource_datasync,base.group_user,1,1,1,1 access_sf_static_resource_datasync,sf_static_resource_datasync,model_sf_static_resource_datasync,base.group_user,1,1,1,1
access_order_price,order.price,model_order_price,base.group_user,1,1,1,1 access_order_price,order.price,model_order_price,sf_base.group_sale_director,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sf_static_resource_datasync sf_static_resource_datasync model_sf_static_resource_datasync base.group_user 1 1 1 1
3 access_order_price order.price model_order_price base.group_user sf_base.group_sale_director 1 1 1 1
4

View File

@@ -8,7 +8,7 @@
<menuitem sequence="22" name="销售订单bfm对比" id="menu_sale_order_bfm_price" <menuitem sequence="22" name="销售订单bfm对比" id="menu_sale_order_bfm_price"
action="order_price_tree_act" action="order_price_tree_act"
parent="sale.sale_order_menu" parent="sale.sale_order_menu"
groups="base.group_user" groups="sf_base.group_sale_director"
/> />
<record id="view_order_price_tree" model="ir.ui.view"> <record id="view_order_price_tree" model="ir.ui.view">

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

View File

@@ -278,6 +278,7 @@
sequence="150" sequence="150"
action="sf_production_plan_action" action="sf_production_plan_action"
groups="sf_base.group_plan_dispatch" groups="sf_base.group_plan_dispatch"
web_icon="sf_plan,static/description/计划.png"
/> />
<!-- <record model="ir.ui.menu" id="mrp_custom_menu" inherit_id="mrp.menu_mrp_manufacturing"> --> <!-- <record model="ir.ui.menu" id="mrp_custom_menu" inherit_id="mrp.menu_mrp_manufacturing"> -->
@@ -338,7 +339,7 @@
name="空料架配送" name="空料架配送"
sequence="11" sequence="11"
action="sf_manufacturing.sf_workpiece_delivery_empty_racks_act" action="sf_manufacturing.sf_workpiece_delivery_empty_racks_act"
groups="base.group_system" groups="sf_base.group_sf_order_user,sf_base.group_sf_mrp_manager,sf_base.group_sf_equipment_user"
parent="mrp.menu_mrp_manufacturing" parent="mrp.menu_mrp_manufacturing"
/> />
<!-- <menuitem --> <!-- <menuitem -->

View File

@@ -74,6 +74,8 @@ class StockPicking(models.Model):
def send_to_bfm(self): def send_to_bfm(self):
skip_backorder = self.env.context.get('skip_backorder') skip_backorder = self.env.context.get('skip_backorder')
cancel_backorder_ids = self.env.context.get('picking_ids_not_to_backorder')
# 下发发货到bfm # 下发发货到bfm
config = self.env['res.config.settings'].get_values() config = self.env['res.config.settings'].get_values()
move_ids, move_line_ids = self.deal_move_ids(self.move_ids, self.move_line_ids) move_ids, move_line_ids = self.deal_move_ids(self.move_ids, self.move_line_ids)
@@ -92,13 +94,13 @@ class StockPicking(models.Model):
'state': self.state, 'state': self.state,
'backorder_id': self.deal_send_backorder_id(self.backorder_id), 'backorder_id': self.deal_send_backorder_id(self.backorder_id),
'backorder_ids': self.deal_send_backorder_id(self.backorder_ids), 'backorder_ids': self.deal_send_backorder_id(self.backorder_ids),
'cancel_backorder_ids': skip_backorder, 'cancel_backorder_ids': True if skip_backorder and cancel_backorder_ids else False, # 没有欠单判断
'move_type': self.move_type, 'move_type': self.move_type,
}, },
} }
url1 = config['bfm_url_new'] + '/api/stock/deliver_goods' url1 = config['bfm_url_new'] + '/api/stock/deliver_goods'
json_str = json.dumps(data) json_str = json.dumps(data)
print('json_str', json_str) logging.info('json_str= %s', json_str)
r = requests.post(url1, json=data, data=None) r = requests.post(url1, json=data, data=None)
if r.status_code == 200: if r.status_code == 200:
result = json.loads(r.json()['result']) result = json.loads(r.json()['result'])

View File

@@ -185,6 +185,7 @@ class CAMWorkOrderProgramKnifePlan(models.Model):
_description = 'CAM工单程序用刀计划' _description = 'CAM工单程序用刀计划'
name = fields.Char('工单任务编号') name = fields.Char('工单任务编号')
programming_no = fields.Char('编程单号')
cam_procedure_code = fields.Char('程序名') cam_procedure_code = fields.Char('程序名')
filename = fields.Char('文件') filename = fields.Char('文件')
cam_cutter_spacing_code = fields.Char('刀号') cam_cutter_spacing_code = fields.Char('刀号')
@@ -317,23 +318,33 @@ class CAMWorkOrderProgramKnifePlan(models.Model):
""" """
根据传入的工单信息查询是否有需要的功能刀具如果没有则生成CAM工单程序用刀计划 根据传入的工单信息查询是否有需要的功能刀具如果没有则生成CAM工单程序用刀计划
""" """
knife_plan = self.env['sf.cam.work.order.program.knife.plan'].sudo().create({
'name': cnc_processing.workorder_id.production_id.name, # 获取编程单号
'cam_procedure_code': cnc_processing.program_name, programming_no = cnc_processing.workorder_id.production_id.programming_no
'filename': cnc_processing.cnc_id.name, logging.info(f'编程单号:{programming_no}')
'functional_tool_name': cnc_processing.cutting_tool_name, cam_id = self.env['sf.cam.work.order.program.knife.plan'].sudo().search(
'cam_cutter_spacing_code': cnc_processing.cutting_tool_no, [('programming_no', '=', programming_no),
'process_type': cnc_processing.processing_type, ('functional_tool_name', '=', cnc_processing.cutting_tool_name)])
'margin_x_y': float(cnc_processing.margin_x_y), logging.info(f'CAM装刀计划{cam_id}')
'margin_z': float(cnc_processing.margin_z), if not cam_id:
'finish_depth': float(cnc_processing.depth_of_processing_z), knife_plan = self.env['sf.cam.work.order.program.knife.plan'].sudo().create({
'extension_length': float(cnc_processing.cutting_tool_extension_length), 'name': cnc_processing.workorder_id.production_id.name,
'shank_model': cnc_processing.cutting_tool_handle_type, 'programming_no': programming_no,
'estimated_processing_time': cnc_processing.estimated_processing_time, 'cam_procedure_code': cnc_processing.program_name,
}) 'filename': cnc_processing.cnc_id.name,
logging.info('CAM工单程序用刀计划创建成功') 'functional_tool_name': cnc_processing.cutting_tool_name,
# 创建装刀请求 'cam_cutter_spacing_code': cnc_processing.cutting_tool_no,
knife_plan.apply_for_tooling() 'process_type': cnc_processing.processing_type,
'margin_x_y': float(cnc_processing.margin_x_y),
'margin_z': float(cnc_processing.margin_z),
'finish_depth': float(cnc_processing.depth_of_processing_z),
'extension_length': float(cnc_processing.cutting_tool_extension_length),
'shank_model': cnc_processing.cutting_tool_handle_type,
'estimated_processing_time': cnc_processing.estimated_processing_time,
})
logging.info('CAM工单程序用刀计划创建成功')
# 创建装刀请求
knife_plan.apply_for_tooling()
def unlink_cam_plan(self, production): def unlink_cam_plan(self, production):
for item in production: for item in production:
@@ -357,6 +368,8 @@ class FunctionalToolAssembly(models.Model):
""" """
智能工厂组装单处扫码校验刀具物料 智能工厂组装单处扫码校验刀具物料
""" """
if 'O-CMD' in barcode:
return ''
for record in self: for record in self:
tool_assembly_id = self.env['sf.functional.tool.assembly'].browse(self.ids) tool_assembly_id = self.env['sf.functional.tool.assembly'].browse(self.ids)
lot_ids = self.env['stock.lot'].sudo().search([('rfid', '=', barcode)]) lot_ids = self.env['stock.lot'].sudo().search([('rfid', '=', barcode)])
@@ -835,7 +848,7 @@ class FunctionalToolAssembly(models.Model):
if options == '刀柄+整体式刀具': if options == '刀柄+整体式刀具':
if not integral_ids: if not integral_ids:
raise ValidationError('功能刀具清单的BOM未配置[刀柄]信息请先配置BOM再开始组装') raise ValidationError('功能刀具清单的BOM未配置[整体式刀具]信息请先配置BOM再开始组装')
return {'options': options, 'handle_ids': handle_ids, 'integral_ids': integral_ids} return {'options': options, 'handle_ids': handle_ids, 'integral_ids': integral_ids}
elif options == '刀柄+刀杆+刀片': elif options == '刀柄+刀杆+刀片':
if not blade_ids: if not blade_ids:

View File

@@ -84,12 +84,12 @@ class jikimo_bom(models.Model):
if option.name == '整体式刀具': if option.name == '整体式刀具':
domain = ['&'] + domain + [ domain = ['&'] + domain + [
'|', '&',
# 刀具直径 # 刀具直径
('cutting_tool_blade_diameter', '=', self.tool_inventory_id.diameter), ('cutting_tool_blade_diameter', '=', self.tool_inventory_id.diameter),
# r角 # r角
('cutting_tool_blade_tip_working_size', '=', self.tool_inventory_id.angle)] ('cutting_tool_blade_tip_r_size', '=', self.tool_inventory_id.angle)]
if option.name == '刀杆': if option.name == '刀杆':
domain = ['&'] + domain + [ domain = ['&'] + domain + [
("cutting_tool_cutter_arbor_diameter", "=", self.tool_inventory_id.diameter)] ("cutting_tool_cutter_arbor_diameter", "=", self.tool_inventory_id.diameter)]

View File

@@ -95,16 +95,20 @@ class CNCprocessing(models.Model):
# tool_state_remark1 = f'{key}无效刀:{data1.get(key)}' # tool_state_remark1 = f'{key}无效刀:{data1.get(key)}'
# 无效刀处理逻辑 # 无效刀处理逻辑
# 1、创建制造订单无效刀检测结果记录 # 1、创建制造订单无效刀检测结果记录
logging.info('创建制造订单无效刀检测结果记录!') if not production_id.detection_result_ids.filtered(
production_id.detection_result_ids.create({ lambda a: (a.processing_panel == key and a.detailed_reason == '无效功能刀具'
'production_id': production_id.id, and a.handle_result == '待处理' and a.routing_type == 'CNC加工'
'processing_panel': key, and a.rework_reason == 'programming' and a.test_results == '返工')):
'routing_type': 'CNC加工', logging.info('创建制造订单无效刀检测结果记录!')
'rework_reason': 'programming', # 原因:编程(programming) production_id.detection_result_ids.create({
'detailed_reason': '无效功能刀具', 'production_id': production_id.id,
'test_results': '返工', 'processing_panel': key,
'handle_result': '待处理' 'routing_type': 'CNC加工',
}) 'rework_reason': 'programming', # 原因:编程(programming)
'detailed_reason': '无效功能刀具',
'test_results': '返工',
'handle_result': '待处理'
})
# 修改当前面装夹预调工单的 is_rework 为 True # 修改当前面装夹预调工单的 is_rework 为 True
# work_ids = production_id.workorder_ids.filtered( # work_ids = production_id.workorder_ids.filtered(
# lambda a: a.routing_type == '装夹预调' and a.processing_panel == key and not a.is_rework) # lambda a: a.routing_type == '装夹预调' and a.processing_panel == key and not a.is_rework)

View File

@@ -298,8 +298,8 @@ class SfShelfLocationLot(models.Model):
brand_id = fields.Many2one('sf.machine.brand', '品牌', related='product_id.brand_id') brand_id = fields.Many2one('sf.machine.brand', '品牌', related='product_id.brand_id')
cutting_tool_blade_diameter = fields.Float('刃部直径(mm)', related='product_id.cutting_tool_blade_diameter') cutting_tool_blade_diameter = fields.Float('刃部直径(mm)', related='product_id.cutting_tool_blade_diameter')
cutting_tool_blade_tip_working_size = fields.Char('刀尖R角(mm)', cutting_tool_blade_tip_working_size = fields.Float('刀尖R角(mm)',
related='product_id.cutting_tool_blade_tip_working_size') related='product_id.cutting_tool_blade_tip_r_size')
cutting_tool_blade_radius = fields.Char('刀尖圆弧半径(mm)', cutting_tool_blade_radius = fields.Char('刀尖圆弧半径(mm)',
related='product_id.cutting_tool_blade_tip_circular_arc_radius') related='product_id.cutting_tool_blade_tip_circular_arc_radius')
cutting_tool_cutter_arbor_diameter = fields.Float('刀杆直径(mm)', cutting_tool_cutter_arbor_diameter = fields.Float('刀杆直径(mm)',

View File

@@ -18,8 +18,7 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree create="0" export_xlsx="0" delete="0"> <tree create="0" export_xlsx="0" delete="0">
<header> <header>
<button string="确认" name="set_tool_material" type="object" <button string="确认" name="set_tool_material" type="object" class="treeHeaderBtn"/>
class="treeHeaderBtn"/>
</header> </header>
<field name="name"/> <field name="name"/>
<field name="cutting_tool_type_id"/> <field name="cutting_tool_type_id"/>
@@ -62,6 +61,7 @@
<field name="brand_id"/> <field name="brand_id"/>
<field name="shelf_location_id"/> <field name="shelf_location_id"/>
<field name="lot_id"/> <field name="lot_id"/>
<field name="qty"/>
</tree> </tree>
</field> </field>
</record> </record>
@@ -86,6 +86,7 @@
<field name="brand_id"/> <field name="brand_id"/>
<field name="shelf_location_id"/> <field name="shelf_location_id"/>
<field name="lot_id"/> <field name="lot_id"/>
<field name="qty"/>
</tree> </tree>
</field> </field>
</record> </record>
@@ -111,6 +112,7 @@
<field name="brand_id"/> <field name="brand_id"/>
<field name="shelf_location_id"/> <field name="shelf_location_id"/>
<field name="lot_id"/> <field name="lot_id"/>
<field name="qty"/>
</tree> </tree>
</field> </field>
</record> </record>
@@ -135,6 +137,7 @@
<field name="brand_id"/> <field name="brand_id"/>
<field name="shelf_location_id"/> <field name="shelf_location_id"/>
<field name="lot_id"/> <field name="lot_id"/>
<field name="qty"/>
</tree> </tree>
</field> </field>
</record> </record>

View File

@@ -283,21 +283,22 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree create="0"> <tree create="0">
<field name="name" string="工单编码"/> <field name="name" string="工单编码"/>
<field name="cam_procedure_code"/> <field name="programming_no"/>
<field name="filename"/> <field name="cam_procedure_code" optional="hide"/>
<field name="filename" optional="hide"/>
<field name="functional_tool_name" string="刀具名称"/> <field name="functional_tool_name" string="刀具名称"/>
<field name="cam_cutter_spacing_code"/> <field name="cam_cutter_spacing_code"/>
<field name="diameter" optional="hide"/> <field name="diameter" optional="hide"/>
<field name="tool_included_angle" optional="hide"/> <field name="tool_included_angle" optional="hide"/>
<field name="process_type"/> <field name="process_type" optional="hide"/>
<field name="margin_x_y"/> <field name="margin_x_y"/>
<field name="margin_z"/> <field name="margin_z"/>
<field name="finish_depth"/> <field name="finish_depth"/>
<field name="extension_length" string="刀具伸出长度(mm)"/> <field name="extension_length" string="刀具伸出长度(mm)"/>
<field name="shank_model"/> <field name="shank_model"/>
<field name="estimated_processing_time"/> <field name="estimated_processing_time"/>
<field name="need_knife_time"/> <field name="need_knife_time" optional="hide"/>
<field name="applicant_time"/> <field name="applicant_time" optional="hide"/>
<field name="plan_execute_status"/> <field name="plan_execute_status"/>
<field name="production_line_id" invisible="1"/> <field name="production_line_id" invisible="1"/>
@@ -332,6 +333,7 @@
</div> </div>
<group> <group>
<group> <group>
<field name="programming_no"/>
<field name="cam_procedure_code"/> <field name="cam_procedure_code"/>
<field name="filename"/> <field name="filename"/>
<field name="production_line_id"/> <field name="production_line_id"/>
@@ -388,6 +390,7 @@
<field name="model">sf.cam.work.order.program.knife.plan</field> <field name="model">sf.cam.work.order.program.knife.plan</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<search> <search>
<field name="programming_no"/>
<field name="name" string="工单编码"/> <field name="name" string="工单编码"/>
<field name="cam_procedure_code"/> <field name="cam_procedure_code"/>
<field name="filename"/> <field name="filename"/>
@@ -478,9 +481,9 @@
class="btn-primary"/> class="btn-primary"/>
<button string="确认组装" name="functional_tool_assembly" type="object" <button string="确认组装" name="functional_tool_assembly" type="object"
attrs="{'invisible': [('assemble_status', 'not in', ['01'])]}" attrs="{'invisible': [('assemble_status', 'not in', ['01'])]}"
class="btn-primary"/> class="btn-primary jikimo_button_confirm"/>
<button name="get_tool_preset_parameter" string="获取测量值" <button name="get_tool_preset_parameter" string="获取测量值"
type="object" class="btn-primary" type="object" class="btn-primary jikimo_button_flushed"
attrs="{'invisible': [('assemble_status', 'in', ['0','1','2'])]}" attrs="{'invisible': [('assemble_status', 'in', ['0','1','2'])]}"
/> />
<field name="assemble_status" widget="statusbar" statusbar_visible="0,01,1"/> <field name="assemble_status" widget="statusbar" statusbar_visible="0,01,1"/>
@@ -1043,7 +1046,7 @@
<field name="res_model">sf.functional.tool.dismantle</field> <field name="res_model">sf.functional.tool.dismantle</field>
<field name="view_mode">tree,form,search</field> <field name="view_mode">tree,form,search</field>
<field name="search_view_id" ref="sf_functional_tool_dismantle_search"/> <field name="search_view_id" ref="sf_functional_tool_dismantle_search"/>
<field name="context">{'search_default_no_dismantle_state':1}</field> <field name="context">{'search_default_no_dismantle_state':1,'is_web_request':True}</field>
</record> </record>
</data> </data>
</odoo> </odoo>

View File

@@ -16,7 +16,7 @@
<record id="view_tool_inventory_inherit_search" model="ir.ui.view"> <record id="view_tool_inventory_inherit_search" model="ir.ui.view">
<field name="name">sf.tool.inventory.inherit.search</field> <field name="name">sf.tool.inventory.inherit.search</field>
<field name="model">sf.tool.inventory</field> <field name="model">sf.tool.inventory</field>
<field name="inherit_id" ref="sf_base.view_cutting_tool_material_search"/> <field name="inherit_id" ref="sf_base.view_cutting_tool_inventory_search"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//field[@name='extension']" position="after"> <xpath expr="//field[@name='extension']" position="after">
<searchpanel> <searchpanel>

View File

@@ -1,138 +0,0 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/

View File

@@ -1,3 +0,0 @@
from . import we_api
from . import models
from . import controllers

View File

@@ -1,52 +0,0 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': '企业微信',
'version': '0.1',
'summary': '企业通讯录\消息处理\企业应用\无缝登录',
'sequence': 30,
"author": 'SmartGo Studio.,',
'description': '''用于企业内部员工的管理,
ER企业微信模块
=====================================================
主要针对odoo使用微信进行管理包括以下功能:
1) 公众号信息管理企业号下多applicaiton管理
2) 接收消息处理
3) 发送消息处理
4) 自定义菜单处理
....
本安装包使用了WechatEnterpriseSDK/wechat_sdk.py在此表示感谢。
源代码可以访问github.地址如下https://github.com/facert/WechatEnterpriseSDK
''',
'category': '基础信息',
'website': 'https://www.smartgo.cn',
'depends': ['base', 'mail','hr'],
'data': [
'security/ir.model.access.csv',
'views/we_config_view.xml',
'views/we_app_view.xml',
'views/we_send_message_view.xml',
'views/we_receive_message_view.xml',
'views/we_message_process_view.xml',
'views/we_templates.xml',
# "views/mail_view.xml",
"views/res_users_view.xml",
'views/menu_view.xml',
# 'views/we_menu.xml',
"data/data.xml"
],
'demo': [
'demo/we_config_demo.xml',
],
'qweb': [
# "static/src/xml/base.xml",
# "static/src/xml/account_payment.xml",
# "static/src/xml/account_report_backend.xml",
],
'installable': True,
'application': True,
'auto_install': False,
# 'post_init_hook': '_auto_install_l10n',
}

View File

@@ -1,273 +0,0 @@
#!/usr/bin/env python
# -*- encoding:utf-8 -*-
""" 对企业微信发送给企业后台的消息加解密示例代码.
@copyright: Copyright (c) 1998-2014 Tencent Inc.
"""
# ------------------------------------------------------------------------
import base64
import string
import random
import hashlib
import time
import struct
from Crypto.Cipher import AES
import xml.etree.cElementTree as ET
import socket
from . import ierror
"""
关于Crypto.Cipher模块ImportError: No module named 'Crypto'解决方案
请到官方网站 https://www.dlitz.net/software/pycrypto/ 下载pycrypto。
下载后按照README中的“Installation”小节的提示进行pycrypto安装。
"""
class FormatException(Exception):
pass
def throw_exception(message, exception_class=FormatException):
"""my define raise exception function"""
raise exception_class(message)
class SHA1:
"""计算企业微信的消息签名接口"""
def getSHA1(self, token, timestamp, nonce, encrypt):
"""用SHA1算法生成安全签名
@param token: 票据
@param timestamp: 时间戳
@param encrypt: 密文
@param nonce: 随机字符串
@return: 安全签名
"""
try:
sortlist = [token, timestamp, nonce, encrypt]
sortlist.sort()
sha = hashlib.sha1()
sort_str = "".join(sortlist)
sha.update(sort_str.encode('utf-8'))
return ierror.WXBizMsgCrypt_OK, sha.hexdigest()
except Exception as e:
return ierror.WXBizMsgCrypt_ComputeSignature_Error, None
class XMLParse:
"""提供提取消息格式中的密文及生成回复消息格式的接口"""
# xml消息模板
AES_TEXT_RESPONSE_TEMPLATE = """<xml>
<Encrypt><![CDATA[%(msg_encrypt)s]]></Encrypt>
<MsgSignature><![CDATA[%(msg_signaturet)s]]></MsgSignature>
<TimeStamp>%(timestamp)s</TimeStamp>
<Nonce><![CDATA[%(nonce)s]]></Nonce>
</xml>"""
def extract(self, xmltext):
"""提取出xml数据包中的加密消息
@param xmltext: 待提取的xml字符串
@return: 提取出的加密消息字符串
"""
try:
xml_tree = ET.fromstring(xmltext)
encrypt = xml_tree.find("Encrypt")
touser_name = xml_tree.find("ToUserName")
return ierror.WXBizMsgCrypt_OK, encrypt.text, touser_name.text
except Exception as e:
return ierror.WXBizMsgCrypt_ParseXml_Error, None, None
def generate(self, encrypt, signature, timestamp, nonce):
"""生成xml消息
@param encrypt: 加密后的消息密文
@param signature: 安全签名
@param timestamp: 时间戳
@param nonce: 随机字符串
@return: 生成的xml字符串
"""
resp_dict = {
'msg_encrypt': encrypt,
'msg_signaturet': signature,
'timestamp': timestamp,
'nonce': nonce,
}
resp_xml = self.AES_TEXT_RESPONSE_TEMPLATE % resp_dict
return resp_xml
class PKCS7Encoder():
"""提供基于PKCS7算法的加解密接口"""
block_size = 32
def encode(self, text):
""" 对需要加密的明文进行填充补位
@param text: 需要进行填充补位操作的明文
@return: 补齐明文字符串
"""
text_length = len(text)
# 计算需要填充的位数
amount_to_pad = self.block_size - (text_length % self.block_size)
if amount_to_pad == 0:
amount_to_pad = self.block_size
# 获得补位所用的字符
pad = chr(amount_to_pad)
return text + pad * amount_to_pad
def decode(self, decrypted):
"""删除解密后明文的补位字符
@param decrypted: 解密后的明文
@return: 删除补位字符后的明文
"""
pad = ord(decrypted[-1])
if pad < 1 or pad > 32:
pad = 0
return decrypted[:-pad]
class Prpcrypt(object):
"""提供接收和推送给企业微信消息的加解密接口"""
def __init__(self, key):
# self.key = base64.b64decode(key+"=")
self.key = key
# 设置加解密模式为AES的CBC模式
self.mode = AES.MODE_CBC
def encrypt(self, text, corpid):
"""对明文进行加密
@param text: 需要加密的明文
@return: 加密得到的字符串
"""
# 16位随机字符串添加到明文开头
text = self.get_random_str() + str(struct.pack("I", socket.htonl(len(text))), encoding='utf8')\
+ str(text, encoding='utf8') + corpid
# 使用自定义的填充方式对明文进行补位填充
pkcs7 = PKCS7Encoder()
text = pkcs7.encode(text)
# 加密
cryptor = AES.new(self.key, self.mode, self.key[:16])
try:
ciphertext = cryptor.encrypt(text)
# 使用BASE64对加密后的字符串进行编码
return ierror.WXBizMsgCrypt_OK, base64.b64encode(ciphertext)
except Exception as e:
return ierror.WXBizMsgCrypt_EncryptAES_Error, None
def decrypt(self, text, corpid):
"""对解密后的明文进行补位删除
@param text: 密文
@return: 删除填充补位后的明文
"""
try:
cryptor = AES.new(self.key, self.mode, self.key[:16])
# 使用BASE64对密文进行解码然后AES-CBC解密
plain_text = cryptor.decrypt(base64.b64decode(text))
except Exception as e:
return ierror.WXBizMsgCrypt_DecryptAES_Error, None
try:
pad = plain_text[-1]
# 去掉补位字符串
# pkcs7 = PKCS7Encoder()
# plain_text = pkcs7.encode(plain_text)
# 去除16位随机字符串
content = plain_text[16:-pad]
xml_len = socket.ntohl(struct.unpack("I", content[: 4])[0])
xml_content = content[4: xml_len + 4]
from_corpid = content[xml_len + 4:]
except Exception as e:
return ierror.WXBizMsgCrypt_IllegalBuffer, None
if str(from_corpid, encoding="utf8") != corpid and len(ET.fromstring(xml_content).findall("SuiteId")) < 1:
return ierror.WXBizMsgCrypt_ValidateCorpid_Error, None
return 0, xml_content
def get_random_str(self):
""" 随机生成16位字符串
@return: 16位字符串
"""
rule = string.ascii_letters + string.digits
str = random.sample(rule, 16)
return "".join(str)
class WXBizMsgCrypt(object):
# 构造函数
# @param sToken: 企业微信后台开发者设置的Token
# @param sEncodingAESKey: 企业微信后台开发者设置的EncodingAESKey
# @param sCorpId: 企业号的CorpId
def __init__(self, sToken, sEncodingAESKey, sCorpId):
try:
self.key = base64.b64decode(sEncodingAESKey + "=")
assert len(self.key) == 32
except:
throw_exception("[error]: EncodingAESKey unvalid !", FormatException)
# return ierror.WXBizMsgCrypt_IllegalAesKey,None
self.m_sToken = sToken
self.m_sCorpid = sCorpId
# 验证URL
# @param sMsgSignature: 签名串对应URL参数的msg_signature
# @param sTimeStamp: 时间戳对应URL参数的timestamp
# @param sNonce: 随机串对应URL参数的nonce
# @param sEchoStr: 随机串对应URL参数的echostr
# @param sReplyEchoStr: 解密之后的echostr当return返回0时有效
# @return成功0失败返回对应的错误码
def VerifyURL(self, sMsgSignature, sTimeStamp, sNonce, sEchoStr):
sha1 = SHA1()
ret, signature = sha1.getSHA1(self.m_sToken, sTimeStamp, sNonce, sEchoStr)
if ret != 0:
return ret, None
if not signature == sMsgSignature:
return ierror.WXBizMsgCrypt_ValidateSignature_Error, None
pc = Prpcrypt(self.key)
ret, sReplyEchoStr = pc.decrypt(sEchoStr, self.m_sCorpid)
return ret, sReplyEchoStr
def EncryptMsg(self, sReplyMsg, sNonce, timestamp=None):
# 将企业回复用户的消息加密打包
# @param sReplyMsg: 企业号待回复用户的消息xml格式的字符串
# @param sTimeStamp: 时间戳可以自己生成也可以用URL参数的timestamp,如为None则自动用当前时间
# @param sNonce: 随机串可以自己生成也可以用URL参数的nonce
# sEncryptMsg: 加密后的可以直接回复用户的密文包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,
# return成功0sEncryptMsg,失败返回对应的错误码None
pc = Prpcrypt(self.key)
ret, encrypt = pc.encrypt(sReplyMsg, self.m_sCorpid)
if ret != 0:
return ret, None
if timestamp is None:
timestamp = str(int(time.time()))
# 生成安全签名
sha1 = SHA1()
ret, signature = sha1.getSHA1(self.m_sToken, timestamp, sNonce, encrypt)
if ret != 0:
return ret, None
xmlParse = XMLParse()
return ret, xmlParse.generate(encrypt, signature, timestamp, sNonce)
def DecryptMsg(self, sPostData, sMsgSignature, sTimeStamp, sNonce):
# 检验消息的真实性,并且获取解密后的明文
# @param sMsgSignature: 签名串对应URL参数的msg_signature
# @param sTimeStamp: 时间戳对应URL参数的timestamp
# @param sNonce: 随机串对应URL参数的nonce
# @param sPostData: 密文对应POST请求的数据
# xml_content: 解密后的原文当return返回0时有效
# @return: 成功0失败返回对应的错误码
# 验证安全签名
xmlParse = XMLParse()
ret, encrypt, touser_name = xmlParse.extract(sPostData)
if ret != 0:
return ret, None
sha1 = SHA1()
ret, signature = sha1.getSHA1(self.m_sToken, sTimeStamp, sNonce, encrypt)
if ret != 0:
return ret, None
if not signature == sMsgSignature:
return ierror.WXBizMsgCrypt_ValidateSignature_Error, None
pc = Prpcrypt(self.key)
ret, xml_content = pc.decrypt(encrypt, self.m_sCorpid)
return ret, xml_content

View File

@@ -1,3 +0,0 @@
from . import wechat_enterprise

View File

@@ -1,20 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#########################################################################
# Author: jonyqin
# Created Time: Thu 11 Sep 2014 01:53:58 PM CST
# File Name: ierror.py
# Description:定义错误码含义
#########################################################################
WXBizMsgCrypt_OK = 0
WXBizMsgCrypt_ValidateSignature_Error = -40001
WXBizMsgCrypt_ParseXml_Error = -40002
WXBizMsgCrypt_ComputeSignature_Error = -40003
WXBizMsgCrypt_IllegalAesKey = -40004
WXBizMsgCrypt_ValidateCorpid_Error = -40005
WXBizMsgCrypt_EncryptAES_Error = -40006
WXBizMsgCrypt_DecryptAES_Error = -40007
WXBizMsgCrypt_IllegalBuffer = -40008
WXBizMsgCrypt_EncodeBase64_Error = -40009
WXBizMsgCrypt_DecodeBase64_Error = -40010
WXBizMsgCrypt_GenReturnXml_Error = -40011

View File

@@ -1,231 +0,0 @@
import time
import functools
import base64
from json import *
from odoo import http, fields
from odoo.http import request
from . import WXBizMsgCrypt
from werkzeug.exceptions import abort
import xml.etree.cElementTree as Et
import requests as req
import logging
from lxml import etree
_logger = logging.getLogger(__name__)
def wechat_login(func):
"""
用来根据userid取得合作伙伴的id
:return:
"""
@functools.wraps(func)
def wrapper(*args, **kw):
# now_time = time.time()
# request.session['session_time'] = time.time()
# if 'session_time' not in request.session:
# request.session['session_time'] = 1
# if now_time > request.session['session_time'] + 350:
_logger.info(u"没进来之前的kw值为%s" % JSONEncoder().encode(kw))
# if not request.session['login'] or not request.session['login'] or request.session[
# 'login'] == "public":
enterprise, agent_id = request.env['we.config'].sudo().get_odoo_wechat()
if enterprise and agent_id:
if 'code' in kw and 'state' in kw: # 检查是否取得了code
if 'kw' in request.session.keys():
_logger.info('code:%s' % kw['code'])
account = enterprise.oauth.get_user_info(code=kw['code'])
_logger.info('account:%s' % account)
if account: # 是否取得了微信企业号通讯录的账号
user_detail = enterprise.user.get_detail(account['user_ticket'])
_logger.info('user_detail:%s' % user_detail)
request.env['we.employee'].we_privacy_update(user_detail)
user = request.env['res.users'].sudo().search(
[('we_employee_id', '=', account['UserId'])])
_logger.info('user:%s' % user)
if user: # 是否取得了用户
if 'state' in kw:
state = base64.b64encode(kw['state'].encode('utf-8')).decode()
kw['state'] = state
uid = request.session.authenticate(request.session.db, user.login,
account['UserId'])
kw['user_id'] = uid
request.session['session_time'] = time.time()
request.session['login'] = user.login
_logger.info(u"进来之后的kw值为%s" % kw)
return func(*args, **kw)
else:
_logger.warning(u'用户不存在.')
return request.render('sg_wechat_enterprise.wechat_warning',
{'title': u'警告', 'content': u'该员工的未配置登录用户.'})
else:
_logger.warning(u'微信企业号验证失败.')
return request.render('sg_wechat_enterprise.wechat_warning',
{'title': u'警告', 'content': u'微信企业号验证失败.'})
else:
# 返回时候进入的地方
del kw['code']
del kw['state']
request.session['kw'] = base64.b64encode(JSONEncoder().encode(kw).encode('utf-8')).decode()
if len(kw) == 0:
base_url = request.httprequest.base_url
else:
base_url = request.httprequest.base_url + '?'
for item, value in kw.items():
base_url += item + '=' + value + "&"
base_url = base_url[: -1]
url = enterprise.oauth.authorize_url(base_url,
state=base64.b64encode(
JSONEncoder().encode(kw).encode('utf-8')).decode(),
agent_id=agent_id,
scope='snsapi_privateinfo')
_logger.warning(u"这是授权的url:" + url)
value = {"url": url}
return request.render("sg_wechat_enterprise.Transfer", value)
else: # 开始微信企业号登录认证
request.session['kw'] = base64.b64encode(JSONEncoder().encode(kw).encode('utf-8')).decode()
if len(kw) == 0:
base_url = request.httprequest.base_url
else:
base_url = request.httprequest.base_url + '?'
for item, value in kw.items():
base_url += item + '=' + value + "&"
base_url = base_url[: -1]
url = enterprise.oauth.authorize_url(base_url,
state=base64.b64encode(
JSONEncoder().encode(kw).encode('utf-8')).decode(),
agent_id=agent_id,
scope='snsapi_privateinfo'
)
_logger.warning(u"这是授权的url:" + url)
value = {"url": url}
return request.render("sg_wechat_enterprise.Transfer", value)
else:
_logger.warning(u'微信企业号初始化失败.')
return request.render('sg_wechat_enterprise.wechat_warning',
{'title': u'警告', 'content': u'微信企业号初始化失败.'})
# return func(*args, **kw)
return wrapper
class WechatEnterprise(http.Controller):
"""
用于接收微信发过来的任何消息,并转发给相应的业务类进行处理
"""
__check_str = 'NDOEHNDSY#$_@$JFDK:Q{!'
BASE_URL = '/we'
@wechat_login
@http.route(BASE_URL + '/auth', type='http', auth='none')
def auth(self, *args, **kw):
"""
企业微信免登认证
"""
try:
# user_id = (request.session['uid'])
redirect1 = kw['redirect'] if 'redirect' in kw else None
uid = kw['user_id'] if 'redirect' in kw else None
_logger.info('user_id %s', uid)
if uid is not False:
request.params['login_success'] = True
if not redirect1:
redirect1 = '/web'
redirect1 = redirect1.replace('-', '&').replace('?', '#')
logging.info('url:%s' % redirect1)
return request.redirect(redirect1)
except Exception as ex:
_logger.error('无有效的登录凭证.')
_logger.warning('auth exceptions:%s' % ex)
return request.render('sg_wechat_enterprise.wechat_warning',
{'title': u'警告', 'content': u'无有效的登录凭证.'})
@http.route('/WechatEnterprise/<string:code>/api', type='http', auth="public", methods=["GET", "POST"], csrf=False)
def process(self, code, **kwargs):
"""
处理从微信服务器发送过来的请求
:param code: 自定义代码
:param kwargs: 包含 (msg_signature, timestamp, nonce, echostr) 等参数
:return:
"""
_logger.info(u'处理从微信服务器发送过来的请求code: %s, kwargs: %s' % (code, kwargs))
app_id = request.env['we.app'].sudo().search([('code', '=', code)], limit=1)
if not app_id:
_logger.warning(u'Can not find wechat app by code: {code}')
abort(403)
corp_id = app_id.enterprise_id
we_chat_cpt = WXBizMsgCrypt.WXBizMsgCrypt(app_id.Token, app_id.EncodingAESKey, corp_id.corp_id)
signature, timestamp, nonce = kwargs['msg_signature'], kwargs['timestamp'], kwargs['nonce']
if kwargs.get('echostr'):
echo_string = kwargs['echostr']
sort_list = [app_id.Token, timestamp, nonce, echo_string]
if request.env['we.tools'].sudo(). \
check_message_signature(message_list=sort_list, msg_signature=signature):
ret, signature_echo_string = we_chat_cpt.VerifyURL(signature, timestamp, nonce, echo_string)
if ret == 0:
return str(signature_echo_string, encoding="utf8")
body_text = request.httprequest.data
ret, signature_message = we_chat_cpt.DecryptMsg(body_text, signature, timestamp, nonce)
xml_tree = Et.fromstring(signature_message)
if len(xml_tree.findall("SuiteId")) > 0:
return "success"
if len(xml_tree.find("ApprovalInfo")) > 0:
xmlstr = etree.fromstring(signature_message)
# data = xml2json_from_elementtree(xmlstr)
return request.env['we.receive.message'].sudo().sys_approval_change(xml_tree.find("ApprovalInfo"))
data = {
'MsgType': xml_tree.find("MsgType").text,
# 'AgentID': xml_tree.find("AgentID").text,
'ToUserName': xml_tree.find("ToUserName").text,
'FromUserName': xml_tree.find("FromUserName").text,
'CreateTime': xml_tree.find("CreateTime").text
}
if xml_tree.find("AgentID") != None:
data['AgentID'] = xml_tree.find("AgentID").text
if data["MsgType"] == "text":
data["Content"] = xml_tree.find("Content").text
data["MsgId"] = xml_tree.find("MsgId").text
if data["MsgType"] == "image":
data["PicUrl"] = xml_tree.find("PicUrl").text
data["MediaId"] = xml_tree.find("MediaId").text
data["MsgId"] = xml_tree.find("MsgId").text
if data["MsgType"] == "voice":
data["MediaId"] = xml_tree.find("MediaId").text
data["Format"] = xml_tree.find("Format").text
data["MsgId"] = xml_tree.find("MsgId").text
if data["MsgType"] == "video" or data["MsgType"] == "shortvideo":
data["MediaId"] = xml_tree.find("MediaId").text
data["ThumbMediaId"] = xml_tree.find("ThumbMediaId").text
data["MsgId"] = xml_tree.find("MsgId").text
if data["MsgType"] == "location":
data["Location_X"] = xml_tree.find("Location_X").text
data["Location_Y"] = xml_tree.find("Location_Y").text
data["Scale"] = xml_tree.find("Scale").text
data["Label"] = xml_tree.find("Label").text
data["MsgId"] = xml_tree.find("MsgId").text
if data["MsgType"] == "link":
data["Title"] = xml_tree.find("Title").text
data["Description"] = xml_tree.find("Description").text
data["PicUrl"] = xml_tree.find("PicUrl").text
data["MsgId"] = xml_tree.find("MsgId").text
if data["MsgType"] == "event":
if xml_tree.find("Event") == "subscribe" or xml_tree.find("Event") == "unsubscribe":
data["Event"] = xml_tree.find("Event").text
else:
ret, signature_message = we_chat_cpt.EncryptMsg(signature_message, nonce, timestamp)
return signature_message
request.env['we.receive.message'].sudo().process_message(data)
return ''
@http.route('/WechatEnterprise/transfer', type='http', auth="public", methods=["POST", "GET"], csrf=False)
def transfer(self, url):
value = {"url": url}
return request.render('sg_wechat_enterprise.Transfer', value)

View File

@@ -1,16 +0,0 @@
<odoo>
<data>
<record model="ir.cron" id="ir_cron_dingtalk_accesstoken">
<field name="name">SNS Message To 企业微信</field>
<field name="model_id" ref="mail.model_mail_message"/>
<field name="state">code</field>
<field name="code">model.send_we_message()</field>
<field name="interval_number">1</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="user_id" ref="base.user_root"/>
<field name="active" eval="False"/>
</record>
</data>
</odoo>

View File

@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="main_wechat_enterprise" model="we.config">
<field name="name">SmartGo</field>
<field name="corp_id">wwad4f2c227d490637</field>
<field name="corp_secret">kq_AzJN1FoPdWjyEwAQs_cqzJhALmKhmwYMBQyJzuEs</field>
<field name="company_id">1</field>
</record>
</data>
</openerp>

View File

@@ -1,9 +0,0 @@
from . import we_app
from . import we_conf
from . import we_send_message
from . import we_receive_message
from . import we_receive_message_process
from . import res_users
from . import we_tools
from . import mail
from . import client

View File

@@ -1,11 +0,0 @@
from wechatpy.enterprise import WeChatClient
from . import api
class JkmWeChatClient(WeChatClient):
def __init__(self, corp_id, secret, session=None):
super(JkmWeChatClient, self).__init__(corp_id, secret, session=session)
oauth = api.JkmWechatOauth()
user = api.JkmWechatUser()

View File

@@ -1,2 +0,0 @@
from .jkm_user import JkmWechatUser
from .jkm_oauth import JkmWechatOauth

View File

@@ -1,53 +0,0 @@
from __future__ import absolute_import, unicode_literals
import six
from wechatpy.client.api.base import BaseWeChatAPI
class JkmWechatOauth(BaseWeChatAPI):
OAUTH_BASE_URL = 'https://open.weixin.qq.com/connect/oauth2/authorize'
def authorize_url(self, redirect_uri, state=None, agent_id=None, scope='snsapi_base'):
"""
构造网页授权链接
详情请参考
https://work.weixin.qq.com/api/doc#90000/90135/91022
:param redirect_uri: 授权后重定向的回调链接地址
:param state: 重定向后会带上 state 参数
:param agent_id: 企业应用的id
:param scope: 应用授权作用域
:return: 返回的 JSON 数据包
"""
redirect_uri = six.moves.urllib.parse.quote(redirect_uri, safe=b'')
url_list = [
self.OAUTH_BASE_URL,
'?appid=',
self._client.corp_id,
'&redirect_uri=',
redirect_uri,
'&response_type=code&scope=',
scope,
]
if state:
url_list.extend(['&state=', state])
url_list.append('#wechat_redirect')
if agent_id:
url_list.extend(['&agentid=', str(agent_id)])
return ''.join(url_list)
def get_user_info(self, code):
"""
获取访问用户身份
详情请参考
https://work.weixin.qq.com/api/doc#90000/90135/91023
:param code: 通过成员授权获取到的code
:return: 返回的 JSON 数据包
"""
return self._get(
'user/getuserinfo',
params={
'code': code,
}
)

View File

@@ -1,21 +0,0 @@
from wechatpy.enterprise.client.api import WeChatUser
class JkmWechatUser(WeChatUser):
def get_detail(self, user_ticket):
"""
获取访问用户敏感信息
详情请参考
https://developer.work.weixin.qq.com/document/path/95833
:param user_ticket: 成员票据
:return: 返回的 JSON 数据包
"""
return self._post(
'user/getuserdetail',
data={
'user_ticket': user_ticket
}
)

View File

@@ -1,121 +0,0 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
import logging
_logger = logging.getLogger(__name__)
# -*- coding: utf-8-*-
import re
##过滤HTML中的标签
#将HTML中标签等信息去掉
#@param htmlstr HTML字符串.
def filter_tags(htmlstr):
#先过滤CDATA
re_cdata=re.compile('//<!\[CDATA\[[^>]*//\]\]>',re.I) #匹配CDATA
re_script=re.compile('<\s*script[^>]*>[^<]*<\s*/\s*script\s*>',re.I)#Script
re_style=re.compile('<\s*style[^>]*>[^<]*<\s*/\s*style\s*>',re.I)#style
re_br=re.compile('<br\s*?/?>')#处理换行
re_h=re.compile('</?\w+[^>]*>')#HTML标签
re_comment=re.compile('<!--[^>]*-->')#HTML注释
s=re_cdata.sub('',htmlstr)#去掉CDATA
s=re_script.sub('',s) #去掉SCRIPT
s=re_style.sub('',s)#去掉style
s=re_br.sub('\n',s)#将br转换为换行
s=re_h.sub('',s) #去掉HTML 标签
s=re_comment.sub('',s)#去掉HTML注释
#去掉多余的空行
blank_line=re.compile('\n+')
s=blank_line.sub('\n',s)
s=replaceCharEntity(s)#替换实体
return s
##替换常用HTML字符实体.
#使用正常的字符替换HTML中特殊的字符实体.
#你可以添加新的实体字符到CHAR_ENTITIES中,处理更多HTML字符实体.
#@param htmlstr HTML字符串.
def replaceCharEntity(htmlstr):
CHAR_ENTITIES={'nbsp':' ','160':' ',
'lt':'<','60':'<',
'gt':'>','62':'>',
'amp':'&','38':'&',
'quot':'"','34':'"',}
re_charEntity=re.compile(r'&#?(?P<name>\w+);')
sz=re_charEntity.search(htmlstr)
while sz:
entity=sz.group()#entity全称如&gt;
key=sz.group('name')#去除&;后entity,如&gt;为gt
try:
htmlstr=re_charEntity.sub(CHAR_ENTITIES[key],htmlstr,1)
sz=re_charEntity.search(htmlstr)
except KeyError:
#以空串代替
htmlstr=re_charEntity.sub('',htmlstr,1)
sz=re_charEntity.search(htmlstr)
return htmlstr
def repalce(s,re_exp,repl_string):
return re_exp.sub(repl_string,s)
class MailMessage(models.Model):
_inherit = 'mail.message'
we_is_send = fields.Selection([('-1', '不需要发送'), ('0', '需要发送'), ('1', '已发送')], string='订订消息发送状态', default='0',
index=True)
we_error_msg = fields.Char('消息不发送原因')
def send_we_message(self):
"""
定时批量发送钉钉消息
:return:
"""
messages = self.search([('we_is_send', '=', '0')])
if messages and len(messages) > 0:
try:
messages.send_message_to_we()
except Exception as ex:
pass
def send_message_to_we(self):
"""
:param agent_id: 必填企业应用的id整型。可在应用的设置页面查看。
:param user_ids: 成员ID列表消息接收者多个接收者用|分隔最多支持1000个
:param title: 标题不超过128个字节超过会自动截断。
:param description: 必填描述不超过512个字节超过会自动截断
:param url: 必填,点击后跳转的链接。
:param btntxt: 按钮文字。 默认为“详情”, 不超过4个文字超过自动截断。
:param party_ids: 部门ID列表。
:param tag_ids: 标签ID列表。
:return:
"""
# 获取配置信息
config = self.env['ir.config_parameter'].sudo()
# 获取wechat
wechat,agent_id = self.env['we.config'].sudo().get_odoo_wechat()
for m in self:
we_user = m.get_we_user()
if we_user and len(we_user)>0:
try:
_logger.info('wechat:%s' % wechat)
model = self.env['ir.model'].search([('model', '=', m.model)], limit=1)
title = "[%s] %s" % (model.name, m.record_name or m.subject or '')
description = filter_tags(m.body) if len(filter_tags(m.body)) > 0 else '有新的状态和数据变更,请注意跟进.'
url = '{base_url}/web#id={id}&model={model}&view_type=form'.format(
base_url=config.get_param('web.base.url'),
id=m.res_id,
model=m.model)
_logger.info('description:%s title:%s' % (description, title))
ret = wechat.message.send_text_card(agent_id, we_user , title, description, url, btntxt='点此跟进..',
party_ids='', tag_ids='')
_logger.info('message_id:%s ret:%s' % (m.message_id, ret))
m.write({'we_is_send':'1','we_error_msg':''})
except Exception as ex:
m.we_error_msg = '向企业微信发送消息失败! 消息编号:%s 原因:%s' % (m.message_id, ex)
_logger.error('向企业微信发送消息失败! 消息编号:%s 原因:%s' % (m.message_id, ex))
else:
m.write({'we_is_send': '-1', 'we_error_msg': '无法发送,没有收件人!'})
_logger.info('message_id:%s 不需要发送,没有收件人!' % m.message_id)
return False

View File

@@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
import random
from odoo import models, fields, api
from odoo.http import request
from odoo.exceptions import AccessDenied
import logging
_logger = logging.getLogger(__name__)
class ResUsers(models.Model):
_inherit = 'res.users'
we_employee_id = fields.Char(string=u'企业微信账号', default="")
def _check_credentials(self, we_employee_id, env):
"""
用户验证
"""
try:
return super(ResUsers, self)._check_credentials(we_employee_id, env)
except AccessDenied:
user_id = self.env['res.users'].sudo().search([('we_employee_id', '=', we_employee_id)])
if not (user_id and user_id.id == self.env.uid):
raise

View File

@@ -1,213 +0,0 @@
from odoo import api, models, exceptions, fields
import logging
from odoo.http import request
_logger = logging.getLogger(__name__)
class WechatEnterpriseApp(models.Model):
_name = 'we.app'
_description = 'Wechat Enterprise App Manage'
agentid = fields.Integer(string=u'应用ID', required=True)
code = fields.Char(string=u'自定义代码')
name = fields.Char(u'企业号应用名称')
description = fields.Text(u'企业号应用详情')
redirect_domain = fields.Char(u'企业应用可信域名')
isreportuser = fields.Selection([('0', u'不接受'), ('1', u'接收')], u'变更通知', required=True, default='0')
isreportenter = fields.Selection([('0', u'不接受'),
('1', u'接收')], u'进入应用事件上报', required=True, default='0')
enterprise_id = fields.Many2one('we.config', string=u'企业微信')
secret = fields.Char(string=u'Secret', size=100)
home_url = fields.Char(u'主页型应用url')
square_logo_url = fields.Char(u'方形头像url')
round_logo_url = fields.Char(u'圆形头像url')
type = fields.Selection([('1', u'消息型'), ('2', u'主页型')], u'应用类型', required=True, default='1')
allow_userinfos = fields.Char(u'企业应用可见范围(人员)')
allow_partys = fields.Char(u'企业应用可见范围(部门)')
allow_tags = fields.Char(u'企业应用可见范围(标签)')
report_location_flag = fields.Selection([('0', u'不上报'), ('1', u'进入会话上报'), ('2', u'持续上报')], u'位置上报',
required=True, default='1')
logo_mediaid = fields.Char(u'企业应用头像的mediaid')
close = fields.Selection([('0', u''), ('1', u'')], u'是否禁用', required=True, default='0')
app_menu_ids = fields.One2many('we.app.menu', 'agentid', string=u'自定义菜单')
Token = fields.Char(u'Token')
EncodingAESKey = fields.Char(u'EncodingAESKey')
def pull_app_from_we(self, wechat):
"""
从微信获取app列表
:return:
"""
try:
app_lists = wechat.agent.list()
if app_lists:
for app_list in app_lists:
if 'agentid' in app_list:
app_detail = wechat.agent.get(app_list['agentid'])
if app_detail:
data = {
'agentid': str(app_detail['agentid'])
}
my_app = request.env["we.app"].search(
[("agentid", "=", str(app_detail['agentid']))])
if my_app and len(my_app) > 0:
continue
data['name'] = app_detail['name']
data['square_logo_url'] = app_detail['square_logo_url']
data['description'] = app_detail['description']
data['close'] = str(app_detail['close'])
data['redirect_domain'] = app_detail['redirect_domain']
data['report_location_flag'] = str(app_detail['report_location_flag'])
data['isreportenter'] = str(app_detail['isreportenter'])
data['enterprise_id'] = self.id
request.env["we.app"].create(data)
except Exception as e:
raise Warning((u'获取应用列表失败,原因:%s', str(e)))
def push_app_to_we(self):
"""
同步app到微信
:return:
"""
wechat = self.env['we.config'].sudo().get_wechat()
if wechat:
try:
for account in self:
app_json = {
'name': account.name
}
if account.description:
app_json['description'] = account.description
if account.redirect_domain:
app_json['redirect_domain'] = account.redirect_domain
app_json['agentid'] = int(account.agentid)
app_json['report_location_flag'] = int(account.report_location_flag)
if account.type == "1": # 消息型应用
if account.name and account.agentid \
and account.isreportuser and account.isreportenter and account.report_location_flag:
app_json['isreportuser'] = int(account.isreportuser)
app_json['isreportenter'] = int(account.isreportenter)
wechat.agent.set(agent_id=app_json['agentid'], name=app_json['name'],
description=app_json['description'],
redirect_domain=app_json['redirect_domain'],
is_report_user=app_json['isreportuser'],
is_report_enter=app_json['isreportenter'],
report_location_flag=app_json['report_location_flag'])
elif account.type == "2": # 主页型应用
if account.name and account.agentid \
and account.report_location_flag and account.home_url:
app_json['home_url'] = account.home_url
wechat.agent.set(agent_id=app_json['agentid'], name=app_json['name'],
description=app_json['description'],
redirect_domain=app_json['redirect_domain'],
is_report_user=app_json['isreportuser'],
is_report_enter=app_json['isreportenter'],
report_location_flag=app_json['report_location_flag'])
except Exception as e:
_logger.warning(u'更新app失败,原因:%s', str(e))
raise Warning(u'更新app失败,原因:%s', str(e))
else:
raise exceptions.Warning(u"初始化企业号失败")
def update_app_menu(self):
"""
同步菜单至app
:return:
"""
wechat = self.env['we.config'].sudo().get_wechat(agentID=self.agentid)
menus = self.env['we.app.menu'].sudo().search([("agentid", "=", self.name)])
wechat.menu.delete(agent_id=self.agentid)
menu_json = {'button': []}
button = []
if wechat and menus:
for menu in menus:
menu_data = {
'name': menu['name']
}
if not menu['partner_menu_id']:
sub_menus = request.env['we.app.menu'].sudo().search(
[("agentid", "=", self.name), ("partner_menu_id", "=", menu['name'])])
if sub_menus and (len(sub_menus) > 0) and (len(sub_menus) < 6):
sub_menu_list = []
for sub_menu in sub_menus:
sub_menu_data = {
'name': sub_menu['name']
}
if menu['type'] == 'xml' or menu['type'] == 'sub_button':
sub_menu_data['type'] = sub_menu['type']
sub_menu_data['url'] = sub_menu['url']
else:
sub_menu_data['type'] = sub_menu['type']
sub_menu_data['key'] = sub_menu['key']
sub_menu_list.append(sub_menu_data)
menu_data['sub_button'] = sub_menu_list
else:
if menu['type'] == 'xml' or menu['type'] == 'sub_button':
menu_data['type'] = menu['type']
menu_data['url'] = menu['url']
else:
menu_data['type'] = menu['type']
menu_data['key'] = menu['key']
button.append(menu_data)
menu_json['button'] = button
wechat.menu.update(agent_id=self.agentid, menu_data=menu_json)
else:
raise exceptions.Warning(u"初始化企业号失败或该应用无菜单")
class WechatEnterpriseAppMenu(models.Model):
_name = 'we.app.menu'
_description = 'Wechat Enterprise App Menu Manage'
agentid = fields.Many2one('we.app', u'企业应用', required=True)
partner_menu_id = fields.Many2one('we.app.menu', u'上级菜单')
type = fields.Selection(
[('sub_button', u'跳转至子菜单'), ('click', u'点击推事件'), ('xml', u'跳转URL'), ('scancode_push', u'扫码推事件'),
('scancode_waitmsg', u'扫码推事件且弹出“消息接收中”提示框'),
('pic_sysphoto', u'弹出系统拍照发图'), ('pic_photo_or_album', u'弹出拍照或者相册发图'), ('pic_weixin', u'弹出微信相册发图器'),
('location_select', u'弹出地理位置选择器')], u'按钮的类型', required=True, default='xml')
name = fields.Char(u'菜单标题', required=True)
key = fields.Char(u'菜单KEY值')
url = fields.Char(u'网页链接')
@api.constrains('partner_menu_id', 'name')
def _check_menu_name_length(self):
if self.name and self.partner_menu_id and len(self.name) > 7:
raise Warning(u'二级菜单显示名称不能超过14个字符或7个汉字.')
elif self.name and not self.partner_menu_id and len(self.name) > 4:
raise Warning(u'一级菜单显示名称不能超过8个字符或4个汉字.')
else:
return True
@api.constrains('agentid')
def check_menu_number(self):
"""
取得一个app的一级菜单量
:return:
"""
menus_ids = self.sudo().search([('agentid', '=', self.agentid['name']), ('partner_menu_id', '=', False)])
if menus_ids and len(menus_ids) > 3:
raise Warning(u'公众号的一级菜单数据不能超过3个.')
return True
@api.constrains('partner_menu_id')
def check_submenu_number(self):
"""
取得一个一级菜单的子菜单数量
:return:
"""
sub_menus_ids = self.sudo().search(
[('partner_menu_id', '=', self.partner_menu_id['name']), ('partner_menu_id', '!=', False)])
if sub_menus_ids and len(sub_menus_ids) > 5:
raise Warning(u'一级菜单的二级子菜单数据不能超过5个.')
return True

View File

@@ -1,62 +0,0 @@
import logging
import requests
import time
import base64
from odoo.http import request
from odoo import api, models, exceptions, fields, _
from wechatpy.enterprise import WeChatClient
from .client import JkmWeChatClient
from json import *
_logger = logging.getLogger(__name__)
class WechatEnterpriseConfigration(models.Model):
_name = 'we.config'
name = fields.Char(u'名称', required=True, help=u'取个好名字吧!')
corp_id = fields.Char(u'企业ID', required=True, help=u'企业ID必填项')
corp_secret = fields.Char(u'通讯录同步Secret', required=True, help=u'通讯录同步Secret,必填项')
odoo_app_id = fields.Many2one('we.app', u'Odoo应用', help=u'在企业微信工作台配置的与Odoo进行连接的应用')
company_id = fields.Many2one('res.company', string=u'所属公司')
_sql_constraints = [
('code_complete_name_uniq', 'unique (company_id)', '一个所属公司只能定义一个企业微信!')
]
def get_wechat(self, agent_id=None, company_id=1):
"""
取得wechat app的实例
:param agent_id: conf or None (是企业号对象还是应用对象)
:return:
"""
enterprise = self.env['we.config'].sudo().search([('company_id', '=', company_id)], limit=1)
if agent_id:
enterprise_app = self.env['we.app'].sudo().search([('agentid', '=', agent_id)])
return WeChatClient(corp_id=enterprise.corp_id, secret=enterprise_app.secret)
return WeChatClient(corp_id=enterprise.corp_id, secret=enterprise.corp_secret)
def get_odoo_wechat(self, company_id=1):
"""
取得Odoo wechat app的实例
:param agent_id: conf or None (是企业号对象还是应用对象)
:return:
"""
enterprise = self.env['we.config'].sudo().search([('company_id', '=', company_id)], limit=1)
if enterprise.odoo_app_id:
return (JkmWeChatClient(corp_id=enterprise.corp_id, secret=enterprise.odoo_app_id.secret),
enterprise.odoo_app_id.agentid)
else:
raise exceptions.Warning(u'Odoo应用未配置. ')
def get_wechat_app(self, app_code=None, company_id=1):
"""
取得wechat app的实例
:param app_code: 应用代码
:return:
"""
enterprise = self.env['we.config'].sudo().search([('company_id', '=', company_id)], limit=1)
if app_code:
enterprise_app = self.env['we.app'].sudo().search([('code', '=', app_code)])
return WeChatClient(corp_id=enterprise.corp_id, secret=enterprise_app.secret)
return WeChatClient(corp_id=enterprise.corp_id, secret=enterprise.corp_secret)

View File

@@ -1,143 +0,0 @@
from odoo import api, models, fields
import logging
from odoo.http import request
import datetime
_logger = logging.getLogger(__name__)
class WechatEnterpriseReceiveMessage(models.Model):
_name = 'we.receive.message'
_description = 'Wechat Enterprise Receive Message Manage'
name = fields.Char(required=True, index=True)
ToUserName = fields.Char(u'企业号CorpID')
FromUserName = fields.Char(u'成员UserID')
CreateTime = fields.Char(u'消息创建时间')
MsgId = fields.Char(u'消息id')
AgentID = fields.Many2one('we.app', u'企业应用')
MsgType = fields.Selection([('text', u'文本'),
('voice', u'语音'),
('image', u'图片'),
('video', u'视频'),
('shortvideo', u'短视频'),
('location', u'位置'),
('link', u'链接'),
('subscribe', u'关注'),
('unsubscribe', u'取消关注'),
('xml', u'自定义菜单链接跳转'),
('click', u'自定义菜单点击'),
('scan', u'扫描二维码'),
('scancode_waitmsg', u'扫描二维码并等待'),
('event', u'取消关注')],
string=u'消息类型', required=True, default='text')
Content = fields.Text(u'文本消息内容')
state = fields.Selection([('1', u'未处理'), ('2', u'已处理'), ('3', u'处理失败')], u'状态', default='1')
PicUrl = fields.Char(u'图片链接')
MediaId = fields.Char(u'图片媒体文件id')
Format = fields.Char(u'语音格式')
ThumbMediaId = fields.Char(u'视频消息缩略图的媒体id')
Location_X = fields.Char(u'地理位置纬度')
Location_Y = fields.Char(u'地理位置经度')
Scale = fields.Char(u'地图缩放大小')
Label = fields.Char(u'地理位置信息')
Title = fields.Char(u'标题')
Description = fields.Char(u'描述')
Cover_PicUrl = fields.Char(u'封面缩略图的url')
@api.depends('MsgType', 'MsgId')
def name_get(self):
result = []
for receive in self:
name = receive.MsgType + '_' + receive.MsgId
result.append((receive.id, name))
return result
def add_message(self, data):
"""
增加一条待处理的上传消息
:param data:
:return:
"""
app = request.env['we.app'].sudo().search([("agentid", "=", data["AgentID"])])
receive_message_data = {
'AgentID': app.id,
'MsgType': data["MsgType"],
'FromUserName': data["FromUserName"],
'ToUserName': data["ToUserName"]
}
current_time = datetime.datetime.now()
real_time = current_time + datetime.timedelta(hours=8)
receive_message_data["CreateTime"] = real_time
receive_message_data["name"] = data["MsgType"] + data["MsgId"]
if data["MsgType"] == "text":
receive_message_data["MsgId"] = data["MsgId"]
receive_message_data["Content"] = data["Content"]
if data["MsgType"] == "image":
receive_message_data["MsgId"] = data["MsgId"]
receive_message_data["PicUrl"] = data["PicUrl"]
receive_message_data["MediaId"] = data["MediaId"]
if data["MsgType"] == "voice":
receive_message_data["MsgId"] = data["MsgId"]
receive_message_data["MediaId"] = data["MediaId"]
receive_message_data["Format"] = data["Format"]
if data["MsgType"] == "video" or data["MsgType"] == "shortvideo":
receive_message_data["MsgId"] = data["MsgId"]
receive_message_data["MediaId"] = data["MediaId"]
receive_message_data["ThumbMediaId"] = data["ThumbMediaId"]
if data["MsgType"] == "location":
receive_message_data["MsgId"] = data["MsgId"]
receive_message_data["Location_X"] = data["Location_X"]
receive_message_data["Location_Y"] = data["Location_Y"]
receive_message_data["Scale"] = data["Scale"]
receive_message_data["Label"] = data["Label"]
if data["MsgType"] == "link":
receive_message_data["MsgId"] = data["MsgId"]
receive_message_data["Title"] = data["Title"]
receive_message_data["Description"] = data["Description"]
receive_message_data["Cover_PicUrl"] = data["PicUrl"]
if data["MsgType"] == "event":
if data["Event"] == "subscribe":
receive_message_data["MsgType"] = "subscribe"
if data["Event"] == "unsubscribe":
receive_message_data["MsgType"] = "unsubscribe"
return super(WechatEnterpriseReceiveMessage, self).create(receive_message_data)
def process_message(self, data):
"""
处理未处理和失败的消息
:param data:
:return:
"""
messages = self.sudo().add_message(data)
for message in messages:
if message:
if message.state == '2':
break
if data["MsgType"] == "text":
process = self.env['we.receive.message.process'].get_message_process(data["MsgType"],
data["Content"],
data["AgentID"])
else:
process = self.env['we.receive.message.process'].get_message_process(data["MsgType"],
" ",
data["AgentID"])
try:
if process:
if data["MsgType"] == "voice" or data["MsgType"] == "image" or data["MsgType"] == "video" or \
data["MsgType"] == "shortvideo":
process.sudo().exec_class_mothed(data["FromUserName"], data["AgentID"], data["MediaId"])
else:
process.sudo().exec_class_mothed(data["FromUserName"], data["AgentID"])
else:
return self.env['we.send.message'].sudo().send_text_message(data["FromUserName"],
data["AgentID"],
content=u'感谢您的关注!')
message.sudo().write({'state': '1'})
except Exception as e:
message.sudo().write({'state': u'处理失败'})
raise Warning(u'处理失败, 原因:%s', str(e))

View File

@@ -1,109 +0,0 @@
from odoo import models, fields
import logging
_logger = logging.getLogger(__name__)
class WechatEnterpriseReceiveMessageProcess(models.Model):
_name = 'we.receive.message.process'
_description = 'Wechat Enterprise Process Receive Process Message'
name = fields.Char(u'名称', help=u'取个好名称方便管理,比如优惠信息索取', required=True)
message_type = fields.Selection([('text', u'文本'),
('voice', u'语音'),
('image', u'图片'),
('video', u'视频'),
('shortvideo', u'短视频'),
('location', u'位置'),
('link', u'链接'),
('subscribe', u'关注'),
('unsubscribe', u'取消关注'),
('xml', u'自定义菜单链接跳转'),
('click', u'自定义菜单点击'),
('scan', u'扫描二维码'),
('scancode_waitmsg', u'扫描二维码并等待'),
('unsubscribe', u'取消关注')],
string=u'消息类型', required=True)
message_key = fields.Char(u'关键字', required=True)
class_name = fields.Char(u'负责处理的类', help='此处填写进行处理的类的名称),例如:topro_service_base.test', required=True, default="类的名称")
method_name = fields.Char(u'负责处理的方法', help='此处填写进入处理的方法名,方法必须包括参数是message和account_id(微信公众号的id),这是一个dict',
required=True, default="方法名")
agentID = fields.Many2one('we.app', u'企业应用', required=True)
note = fields.Text(u'备注')
def get_message_process(self, message_type, key_word=False, agent_id=False):
"""
取得消息处理的设置
:param message_type:
:param key_word:
:param agent_id:
:return:
"""
process = False
if message_type:
process = self.sudo().search(
[('message_type', '=', message_type), ('message_key', '=', key_word), ('agentID', '=', agent_id)],
limit=1)
if not process and message_type and key_word:
process = self.sudo().search([('message_type', '=', message_type), ('message_key', '=', key_word)], limit=1)
if not process and message_type:
process = self.sudo().search([('message_type', '=', message_type)], limit=1)
return process
def exec_by_message_type(self, message_type, message_key, agent_id):
"""
根据消息的类型动态调用类进行执行
:param message_type:
:param message_key:
:param agent_id:
:return:
"""
# 取得对象,
if message_type and message_key:
process = self.get_message_process(message_type, message_key, agent_id)
process.sudo().exec_class_mothed(message_key, agent_id)
def exec_class_mothed(self, from_user_name, agent_id, media_id=None):
"""
执行类的方法
:param from_user_name:
:param agent_id:
:param media_id:
:return:
"""
_logger.debug('exec_class_mothed')
object_function = getattr(self.env[self.class_name], self.method_name)
ret = object_function(from_user_name, agent_id, media_id)
return ret
def hello(self, from_user_name, agent_id):
"""
demo方法模拟动态处理客户的需求
:param from_user_name:
:param agent_id:
:return:
"""
try:
self.env['we.send.message'].sudo(). \
send_news_message(from_user_name, agent_id, u'测试图文信息', u'测试图文信息的描述',
'http://www.baidu.com',
'http://www.kia-hnsyt.com.cn/uploads/allimg/141204/1-1412041624240-L.jpg')
except Exception as e:
_logger.warning(u'发送微信文本失败原因:%s', str(e))
raise Warning(str(e))
def send_img(self, from_user_name, agent_id):
"""
demo方法模拟动态处理客户的需求
:param from_user_name:
:param agent_id:
:return:
"""
try:
self.env['we.send.message'].sudo().send_text_message(from_user_name, agent_id, u'即将为您发送一条消息')
except Exception as e:
_logger.warning(u'发送微信文本失败原因:%s', str(e))
raise Warning(str(e))

View File

@@ -1,108 +0,0 @@
from odoo import api, models, exceptions, fields
import logging
_logger = logging.getLogger(__name__)
class WechatEnterpriseSendMessage(models.Model):
_name = 'we.send.message'
_description = 'Wechat Enterprise Send Message Manage'
touser = fields.Many2many('res.users', 'send_message_to_users_ref',
'wechat_contacts_id', 'touser', u'成员列表')
msgtype = fields.Selection([('text', u'文字消息'), ('image', u'图片消息'), ('voice', u'语音消息'), ('video', u'视频消息'),
('file', u'文件消息'), ('news', u'图文消息'), ('mpnews', u'微信后台图文消息')], u'消息类型',
required=True, default='text')
agentid = fields.Many2one('we.app', u'发送消息的企业应用', required=True)
content = fields.Char(u'消息内容')
media_id = fields.Char(u'媒体文件')
title = fields.Char(u'标题')
description = fields.Text(u'描述')
articles = fields.Char(u'图文消息')
url = fields.Char(u'点击后跳转的链接')
picurl = fields.Char(u'图文消息的图片链接')
thumb_media_id = fields.Char(u'图文消息缩略图')
author = fields.Char(u'图文消息的作者')
content_source_url = fields.Char(u'图文消息点击“阅读原文”之后的页面链接')
news_content = fields.Char(u'图文消息的内容支持html标签')
digest = fields.Char(u'图文消息的描述')
show_cover_pic = fields.Selection([('0', u''), ('1', u'')], u'是否显示封面', default='0')
safe = fields.Selection([('0', u''), ('1', u'')], u'是否是保密消息', required=True, default='0')
def send_message(self):
"""
发送消息给关注企业号的用户
:return:
"""
users = ""
i = 0
if self.touser and len(self.touser) > 0:
for data in self.touser:
i = i + 1
if i == len(self.touser):
if data['we_employee_id']:
users = users + data['we_employee_id']
else:
if data['we_employee_id']:
users = users + data['we_employee_id'] + "|"
if users == "":
users = '@all'
partys = ""
if self.msgtype == "news":
self.send_news_message(users, self.agentid['agentid'], self.title, self.description, self.url, self.picurl)
elif self.msgtype == "text":
self.send_text_message(users, self.agentid['agentid'], self.content, partys)
def send_text_message(self, userid, agentid, content, partyid=None):
"""
发送文本消息给关注企业号的用户
:param userid:
:param agentid:
:param content:
:param partyid:
:return:
"""
try:
wechat = self.env['we.config'].sudo().get_wechat(agent_id=agentid)
if wechat:
data = {
'safe': "0",
'msgtype': 'text',
'agentid': agentid,
'touser': userid,
'toparty': partyid,
'content': content
}
wechat.message.send_text(agent_id=data['agentid'], user_ids=data['touser'], content=data['content'],
party_ids=data['toparty'], safe=data['safe'])
else:
raise exceptions.Warning(u"初始化企业号失败")
except Exception as e:
logging.error('send_text_message:%s' % e)
# 发送图文消息给关注企业号的用户
def send_news_message(self, userid, agentid, title=None, description=None, url=None, picurl=None):
"""
发送图文消息给关注企业号的用户
:param userid:
:param agentid:
:param title:
:param description:
:param url:
:param picurl:
:return:
"""
wechat = self.env['we.config'].sudo().get_wechat(agent_id=agentid)
if wechat:
articles = [
{
'url': url,
'image': picurl,
'description': description,
'title': title
}
]
wechat.message.send_articles(agent_id=agentid, user_ids=userid, articles=articles)
else:
raise exceptions.Warning(u"初始化企业号失败")

View File

@@ -1,89 +0,0 @@
from odoo import api, models, exceptions
from odoo.http import request
import logging
import hashlib
import base64
import time
import requests
_logger = logging.getLogger(__name__)
class WechatEnterpriseTools(models.Model):
"""
微信企业号工具类
"""
_name = 'we.tools'
_description = '微信企业号工具类'
def get_media(self, media_id):
"""
通过media_id 获取媒体文件
:param media_id: media id
:return:
"""
wechat = self.env['we.config'].sudo().get_wechat()
try:
media = wechat.media.download(media_id=media_id)
return {
'errcode': 0,
'errmsg': 'ok',
'media': media.content
}
except Exception as ex:
_logger.info(u'get media fail, message: {str(ex)}.')
return {
'errcode': 30001,
'errmsg': str(ex)
}
def get_jsapi_ticket(self):
"""
获取jsapi_ticket
:return:
"""
if request.session.get('ticket') and request.session.get('ticket_time') \
and int(time.time()) - request.session['ticket_time'] <= 7000:
return {
'errcode': 0,
'errmsg': 'ok',
'ticket': request.session['ticket']
}
wechat = self.env['we.config'].sudo().get_wechat()
get_token = wechat.fetch_access_token()
if get_token['errcode'] == 0 and get_token['errmsg'] == 'ok':
access_token = get_token['access_token']
url = u'https://qyapi.weixin.qq.com/cgi-bin/get_jsapi_ticket?access_token={access_token}'
response = requests.get(url).json()
if response['errcode'] == 0 and response['errmsg'] == 'ok':
request.session['ticket'] = response['ticket']
request.session['ticket_time'] = int(time.time())
return response
return {
"errcode": 10002,
"errmsg": "get ticket fail."
}
def check_message_signature(self, message_list, msg_signature):
"""
校验消息的正确性
:param message_list: 消息列表 (list: token, timestamp, nonce, echo_string)
:param msg_signature: 签名
:return: true or false
"""
_logger.info(u'check message signature.')
message_list.sort()
message_str = "".join(message_list)
sha1_message = hashlib.sha1(str(message_str).encode('utf-8')).hexdigest()
if sha1_message == msg_signature:
return True
return False
def decode_echo_str(self, echo_str):
"""
解密echo string 得到明文内容
:param echo_str: 加密字符串
:return: message
"""
_logger.info(u'decode echo string.')
base64_str = base64.b64decode(echo_str)

View File

@@ -1 +0,0 @@
wechatpy==1.8.6

View File

@@ -1,13 +0,0 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_user_we_app,we_app,model_we_app,,1,1,1,1
access_user_we_config,we_config,model_we_config,,1,1,1,1
access_user_we_receive_message,we_receive_message,model_we_receive_message,,1,1,1,1
access_user_we_receive_message_process,we_receive_message_process,model_we_receive_message_process,,1,1,1,1
access_user_we_send_message,we_send_message,model_we_send_message,,1,1,1,1
access_user_we_app_menu,we_app_menu,model_we_app_menu,,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_user_we_app we_app model_we_app 1 1 1 1
3 access_user_we_config we_config model_we_config 1 1 1 1
4 access_user_we_receive_message we_receive_message model_we_receive_message 1 1 1 1
5 access_user_we_receive_message_process we_receive_message_process model_we_receive_message_process 1 1 1 1
6 access_user_we_send_message we_send_message model_we_send_message 1 1 1 1
7 access_user_we_app_menu we_app_menu model_we_app_menu 1 1 1 1

View File

@@ -1,24 +0,0 @@
@-webkit-keyframes loadingCircle {
to {
transform:rotate(1turn)
}
}
@keyframes loadingCircle {
to {
transform:rotate(1turn)
}
}
.load-box {
position:fixed;
top:calc(50% - 11px);
left:calc(50% - 111px);
display:flex;
align-items:center;
}
.load-icon {
animation:loadingCircle 1s linear infinite;
}
.load-txt {
font-size:16px;
margin-left:10px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

View File

@@ -1,26 +0,0 @@
/**
* Created by jiangxiang on 2016/3/14.
*/
//<2F><>ȡ<EFBFBD><C8A1><EFBFBD>Ӵ<EFBFBD><D3B4><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>openID<49><44><EFBFBD><EFBFBD>
function getUrlParam(url, name) {
var pattern = new RegExp("[?&]" + name + "\=([^&]+)", "g");
var matcher = pattern.exec(url);
var items = null;
if (matcher != null) {
try {
items = decodeURIComponent(decodeURIComponent(matcher[1]));
} catch (e) {
try {
items = decodeURIComponent(matcher[1]);
} catch (e) {
items = matcher[1];
}
}
}
items = items.replace(/^\s*/, "");
return items;
}
var url = document.getElementById("url").innerText;
window.location.href = url;

View File

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

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