Compare commits

..

45 Commits

Author SHA1 Message Date
禹翔辉
06e9d5a538 Accept Merge Request #2004: (feature/功能刀具拆解单优化_1 -> develop)
Merge Request: Merge branch 'feature/功能刀具拆解单优化' into feature/功能刀具拆解单优化_1

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

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2003?initial=true
2025-04-15 17:14:51 +08:00
胡尧
20722c12b8 修改质检单的质检员字段 2025-04-15 17:08:43 +08:00
禹翔辉
56f2ea0356 Accept Merge Request #2002: (feature/功能刀具拆解单优化 -> develop)
Merge Request: 功能刀具添加状态变更跟踪

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2002
2025-04-15 16:58:28 +08:00
yuxianghui
bc85c457ad 功能刀具添加状态变更跟踪 2025-04-15 16:46:49 +08:00
禹翔辉
7eeea92a3e Accept Merge Request #2001: (feature/功能刀具拆解单优化 -> develop)
Merge Request: 功能刀具拆解单添加可以选择线边刀库的功能刀具进行拆解

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2001
2025-04-15 15:09:36 +08:00
yuxianghui
d672f3f4d7 功能刀具拆解单添加可以选择线边刀库的功能刀具进行拆解 2025-04-15 15:05:23 +08:00
黄焱
c79cf2e5ad Accept Merge Request #2000: (feature/前端样式修改 -> develop)
Merge Request: 删除多余字段

Created By: @黄焱
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @黄焱
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2000?initial=true
2025-04-15 11:26:42 +08:00
hyyy
33a5fc0ff4 删除多余字段 2025-04-15 11:24:45 +08:00
黄焱
2dcaa25952 Accept Merge Request #1999: (feature/前端样式修改 -> develop)
Merge Request: 修改刀片刀杆样式

Created By: @黄焱
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @黄焱
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1999
2025-04-15 09:43:20 +08:00
hyyy
ba88070fad 修改刀片刀杆样式 2025-04-15 09:31:42 +08:00
马广威
3f940992be Accept Merge Request #1998: (feature/制造功能优化 -> develop)
Merge Request: 调整报告,增加稼动率接口(暂未调用)

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1998?initial=true
2025-04-14 17:24:04 +08:00
mgw
197ae6bc01 去掉基础数据 2025-04-14 17:22:27 +08:00
mgw
07336326ce 优化稼动率接口 2025-04-14 17:16:38 +08:00
mgw
c93553e78e 增加稼动率接口 2025-04-14 16:42:33 +08:00
mgw
91d79008e1 故障时长提取 2025-04-14 11:21:24 +08:00
禹翔辉
8cdf77f609 Accept Merge Request #1997: (feature/功能刀具组装初始化优化 -> develop)
Merge Request: 功能刀具组装自动根据BOM配置初始物料信息优化

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1997
2025-04-14 10:34:47 +08:00
yuxianghui
37edc858c2 1 2025-04-14 10:32:26 +08:00
yuxianghui
17fdf20e03 功能刀具组装自动根据BOM配置初始物料信息优化 2025-04-14 10:30:51 +08:00
胡尧
ec934abc42 Accept Merge Request #1996: (feature/6679 -> develop)
Merge Request: 给仓储岗增加修改工单的权限

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1996
2025-04-11 16:04:02 +08:00
mgw
87786dbd80 调整故障时长 2025-04-11 14:16:18 +08:00
禹翔辉
9d0ffd23b2 Accept Merge Request #1995: (feature/刀具组装扫描优化 -> develop)
Merge Request: 处理功能刀具组装时,有时扫描货位编码验证刀具信息扫不到货位的问题

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1995
2025-04-10 14:27:04 +08:00
yuxianghui
3fb56f15c8 处理功能刀具组装时,有时扫描货位编码验证刀具信息扫不到货位的问题 2025-04-10 14:24:25 +08:00
mgw
bdf4696c08 调整待完成工单明细 2025-04-09 16:56:53 +08:00
mgw
6c926bf081 调整质检取值 2025-04-09 16:39:21 +08:00
mgw
ddb0c304b9 修改字段路径 2025-04-09 15:57:13 +08:00
mgw
cf8c14e738 调整查询范围 2025-04-09 15:22:41 +08:00
黄焱
6bd6816495 Accept Merge Request #1994: (feature/前端样式修改 -> develop)
Merge Request: 功能刀具组装优化需求

Created By: @黄焱
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @黄焱
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1994?initial=true
2025-04-09 14:03:06 +08:00
hyyy
2bae98950e 功能刀具组装优化需求 2025-04-09 13:57:48 +08:00
胡尧
ec379a7541 Accept Merge Request #1993: (feature/6679 -> develop)
Merge Request: 增加不同工作中心配置相同接驳站的功能

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

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

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

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

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1989
2025-04-08 10:11:41 +08:00
mgw
e61742cc5b Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化 2025-04-08 10:11:05 +08:00
mgw
d0d1a640d9 调整报告页码 2025-04-08 10:10:36 +08:00
禹翔辉
e613a2f283 Accept Merge Request #1987: (feature/功能刀具拆解优化 -> develop)
Merge Request: 新增对寿命未到期且位置在线边刀库的功能刀具进行拆解时,不直接报错,而是进行二次确认是否进行拆除。

Created By: @禹翔辉
Reviewed By: @马广威
Approved By: @马广威 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1987
2025-04-08 09:59:09 +08:00
马广威
88e83c0e14 Accept Merge Request #1988: (feature/制造功能优化 -> develop)
Merge Request: 调整报告产品取值

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1988
2025-04-08 09:48:48 +08:00
mgw
f912a81e7b 调整报告产品取值 2025-04-08 09:48:05 +08:00
yuxianghui
e09226e966 新增对寿命未到期且位置在线边刀库的功能刀具进行拆解时,不直接报错,而是进行二次确认是否进行拆除。 2025-04-08 09:44:46 +08:00
胡尧
8a7a90ff0d agv配置不可修改 2025-04-07 17:04:34 +08:00
18 changed files with 679 additions and 114 deletions

View File

@@ -206,7 +206,7 @@ class QualityCheck(models.Model):
('NG', 'NG') ('NG', 'NG')
], string='出厂检验报告结果', default='OK') ], string='出厂检验报告结果', default='OK')
measure_operator = fields.Many2one('res.users', string='操机员') measure_operator = fields.Many2one('res.users', string='操机员')
quality_manager = fields.Many2one('res.users', string='质检员', compute='_compute_quality_manager', store=True) quality_manager = fields.Many2one('res.users', string='质检员', compute='_compute_quality_manager')
@api.depends('measure_line_ids') @api.depends('measure_line_ids')
def _compute_quality_manager(self): def _compute_quality_manager(self):

View File

@@ -267,7 +267,7 @@
<field name="company_id" invisible="1"/> <field name="company_id" invisible="1"/>
<field name="categ_type" invisible="1"/> <field name="categ_type" invisible="1"/>
<field name="product_id" attrs="{'invisible' : [('measure_on', '=', 'operation')]}"/> <field name="product_id" attrs="{'invisible' : [('measure_on', '=', 'operation')]}"/>
<field name="part_name" attrs="{'invisible': [('categ_type', '!=', '成品')]}"/> <field name="part_name" attrs="{'invisible': [('categ_type', '!=', '成品')], 'readonly': [('publish_status', '=', 'published')]}"/>
<field name="part_number" attrs="{'invisible': [('categ_type', '!=', '成品')], 'readonly': [('publish_status', '=', 'published')]}"/> <field name="part_number" attrs="{'invisible': [('categ_type', '!=', '成品')], 'readonly': [('publish_status', '=', 'published')]}"/>
<field name="material_name" attrs="{'invisible': [('categ_type', '!=', '成品')]}"/> <field name="material_name" attrs="{'invisible': [('categ_type', '!=', '成品')]}"/>
<field name="total_qty" attrs="{'invisible': ['|', ('measure_on', '!=', 'product'), ('is_out_check', '=', False)]}"/> <field name="total_qty" attrs="{'invisible': ['|', ('measure_on', '!=', 'product'), ('is_out_check', '=', False)]}"/>

View File

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

View File

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

View File

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

View File

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

View File

@@ -38,6 +38,7 @@
'web.assets_qweb': [ 'web.assets_qweb': [
], ],
'web.assets_backend': [ 'web.assets_backend': [
'sf_tool_management/static/src/change.scss'
] ]
}, },

View File

@@ -387,7 +387,7 @@ class FunctionalToolAssembly(models.Model):
else: else:
raise ValidationError('刀柄选择错误,请重新确认!!!') raise ValidationError('刀柄选择错误,请重新确认!!!')
else: else:
location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', barcode)]) location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', barcode.upper())])
if location: if location:
if location == record.integral_freight_barcode_id: if location == record.integral_freight_barcode_id:
tool_assembly_id.integral_verify = True tool_assembly_id.integral_verify = True
@@ -782,10 +782,11 @@ class FunctionalToolAssembly(models.Model):
"""根据BOM对刀具物料进行初始配置""" """根据BOM对刀具物料进行初始配置"""
options = bom.get('options') options = bom.get('options')
# 配置刀柄信息 # 配置刀柄信息
for handle_id in bom.get('handle_ids'): handle_ids = self._get_old_tool_material_lot(bom.get('handle_ids'))
for handle_id in handle_ids:
if handle_id: if handle_id:
if not self.handle_product_id: if not self.handle_product_id:
self.handle_product_id = handle_id.id self.handle_product_id = handle_id.product_id.id
break break
# 刀柄之外的物料配置 # 刀柄之外的物料配置
@@ -820,19 +821,20 @@ class FunctionalToolAssembly(models.Model):
location_id = self.env['stock.location'].search([('name', '=', '刀具房')]) location_id = self.env['stock.location'].search([('name', '=', '刀具房')])
stock_quant = self.env['stock.quant'].sudo().search( stock_quant = self.env['stock.quant'].sudo().search(
[('location_id', '=', location_id.id), ('product_id', 'in', material_ids.ids), ('quantity', '>', '0')], [('location_id', '=', location_id.id), ('product_id', 'in', material_ids.ids), ('quantity', '>', '0')],
order='lot_id', limit=1) order='lot_id')
if stock_quant: if stock_quant:
return stock_quant.lot_id return [quant.lot_id for quant in stock_quant]
else: else:
raise ValidationError(f'{material_ids[0].cutting_tool_material_id.name}】物料库存不足,请先进行盘点或采购') raise ValidationError(f'{material_ids[0].cutting_tool_material_id.name}】物料库存不足,请先进行盘点或采购')
def _get_shelf_location_lot(self, lot_id): def _get_shelf_location_lot(self, lot_ids):
"""根据所给的刀具物料批次号,返回一个刀具物料货位、批次信息""" """根据所给的刀具物料批次号,返回一个刀具物料货位、批次信息"""
for lot_id in lot_ids:
location_lots = self.env['sf.shelf.location.lot'].sudo().search([('lot_id', '=', lot_id.id)]) location_lots = self.env['sf.shelf.location.lot'].sudo().search([('lot_id', '=', lot_id.id)])
if not location_lots: if location_lots:
raise ValidationError(f'没有查询到批次为【{lot_id.name}】物料的货位信息!')
else:
return location_lots[0] return location_lots[0]
raise ValidationError(f'{lot_ids[0].product_id.cutting_tool_material_id.name}】物料在货位库存不足,请先进行盘点入库')
def _get_inventory_bom(self, inventory_id): def _get_inventory_bom(self, inventory_id):
"""获取BOM的刀具物料产品信息""" """获取BOM的刀具物料产品信息"""
@@ -1240,7 +1242,7 @@ class FunctionalToolDismantle(models.Model):
functional_tool_id = fields.Many2one('sf.functional.cutting.tool.entity', '功能刀具', required=True, tracking=True, functional_tool_id = fields.Many2one('sf.functional.cutting.tool.entity', '功能刀具', required=True, tracking=True,
domain=[('functional_tool_status', '!=', '已拆除'), domain=[('functional_tool_status', '!=', '已拆除'),
('current_location', '=', '刀具房')]) ('current_location', 'in', ['刀具房', '线边刀库'])])
@api.onchange('functional_tool_id') @api.onchange('functional_tool_id')
def _onchange_functional_tool_id(self): def _onchange_functional_tool_id(self):
@@ -1437,14 +1439,26 @@ class FunctionalToolDismantle(models.Model):
def confirmation_disassembly(self): def confirmation_disassembly(self):
logging.info('%s刀具确认开始拆解' % self.dismantle_cause) logging.info('%s刀具确认开始拆解' % self.dismantle_cause)
code = self.code code = self.code
context = self.env.context
if self.functional_tool_id.functional_tool_status == '已拆除': if self.functional_tool_id.functional_tool_status == '已拆除':
raise ValidationError('Rfid为【%s】名称为【%s】的功能刀具已经拆解,请勿重复操作!' % ( raise ValidationError('Rfid为【%s】名称为【%s】的功能刀具已经拆解,请勿重复操作!' % (
self.functional_tool_id.rfid_dismantle, self.name)) self.functional_tool_id.rfid_dismantle, self.name))
# 对拆解的功能刀具进行校验,只有在刀具房的功能刀具才能拆解 # 对拆解的功能刀具进行校验,只有在刀具房的功能刀具才能拆解
elif self.functional_tool_id.functional_tool_status != '报警': elif self.functional_tool_id.functional_tool_status != '报警':
if self.functional_tool_id.tool_room_num == 0: if self.functional_tool_id.current_location == '机内刀库':
raise ValidationError('Rfid为【%s】的功能刀具当前位置为【%s】,不能进行拆解!' % ( raise ValidationError('Rfid为【%s】的功能刀具当前位置为【%s】,不能进行拆解!' % (
self.rfid, self.functional_tool_id.current_location)) self.rfid, self.functional_tool_id.current_location))
elif not context.get('TRUE_DISASSEMBLE') and self.functional_tool_id.current_location == '线边刀库':
return {
'type': 'ir.actions.act_window',
'res_model': 'sf.functional.tool.dismantle.wiard',
'name': '刀具寿命未到期',
'view_mode': 'form',
'target': 'new',
'context': {
'default_functional_tool_dismantle_id': self.id,
'TRUE_DISASSEMBLE': True}
}
# 目标重复校验 # 目标重复校验
self.location_duplicate_check() self.location_duplicate_check()
datas = {'scrap': [], 'picking': []} datas = {'scrap': [], 'picking': []}
@@ -1521,11 +1535,18 @@ class FunctionalToolDismantle(models.Model):
'functional_tool_name': self.functional_tool_id.name, 'functional_tool_name': self.functional_tool_id.name,
'handle_code_id': self.handle_lot_id.id, 'handle_code_id': self.handle_lot_id.id,
'handle_product_id': self.handle_product_id.id, 'handle_product_id': self.handle_product_id.id,
'functional_tool_diameter': self.functional_tool_id.functional_tool_diameter,
'knife_tip_r_angle': self.functional_tool_id.knife_tip_r_angle,
'tool_loading_length': self.functional_tool_id.tool_loading_length,
'functional_tool_length': self.functional_tool_id.functional_tool_length,
'loading_task_source': '3', 'loading_task_source': '3',
'use_tool_time': fields.Datetime.now() + timedelta(hours=4), 'use_tool_time': fields.Datetime.now() + timedelta(hours=4),
'reason_for_applying': '刀具寿命到期' 'reason_for_applying': '刀具寿命到期'
}) })
# 将新的组装单更新到对应的功能刀具安全库存的组装单列表中
self.functional_tool_id.safe_inventory_id.sudo().sf_functional_tool_assembly_ids = [(4, assembly_id.id)]
return { return {
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
'res_model': 'sf.functional.tool.assembly', 'res_model': 'sf.functional.tool.assembly',

View File

@@ -10,6 +10,7 @@ from odoo.exceptions import ValidationError
class FunctionalCuttingToolEntity(models.Model): class FunctionalCuttingToolEntity(models.Model):
_name = 'sf.functional.cutting.tool.entity' _name = 'sf.functional.cutting.tool.entity'
_inherit = ['mail.thread']
_description = '功能刀具列表' _description = '功能刀具列表'
_order = 'functional_tool_status' _order = 'functional_tool_status'
@@ -41,7 +42,7 @@ class FunctionalCuttingToolEntity(models.Model):
max_lifetime_value = fields.Integer(string='最大寿命值(min)', readonly=True) max_lifetime_value = fields.Integer(string='最大寿命值(min)', readonly=True)
alarm_value = fields.Integer(string='报警值(min)', readonly=True) alarm_value = fields.Integer(string='报警值(min)', readonly=True)
used_value = fields.Integer(string='已使用值(min)', readonly=True) used_value = fields.Integer(string='已使用值(min)', readonly=True)
functional_tool_status = fields.Selection([('正常', '正常'), ('报警', '报警'), ('已拆除', '已拆除')], functional_tool_status = fields.Selection([('正常', '正常'), ('报警', '报警'), ('已拆除', '已拆除')], tracking=True,
string='状态', store=True, default='正常') string='状态', store=True, default='正常')
current_location_id = fields.Many2one('stock.location', string='当前位置', compute='_compute_current_location_id', current_location_id = fields.Many2one('stock.location', string='当前位置', compute='_compute_current_location_id',
store=True) store=True)
@@ -62,6 +63,17 @@ class FunctionalCuttingToolEntity(models.Model):
for item in self: for item in self:
if item: if item:
if item.functional_tool_status == '报警': if item.functional_tool_status == '报警':
self.create_tool_dismantle()
def set_functional_tool_status(self):
# self.write({
# 'functional_tool_status': '报警'
# })
self.functional_tool_status = '报警'
self.create_tool_dismantle()
def create_tool_dismantle(self):
for item in self:
# 创建报警刀具拆解单 # 创建报警刀具拆解单
self.env['sf.functional.tool.dismantle'].sudo().create({ self.env['sf.functional.tool.dismantle'].sudo().create({
'functional_tool_id': item.ids[0], 'functional_tool_id': item.ids[0],
@@ -263,7 +275,7 @@ class FunctionalCuttingToolEntity(models.Model):
functional_tool_model_ids.append(functional_tool_model.id) functional_tool_model_ids.append(functional_tool_model.id)
return [(6, 0, functional_tool_model_ids)] return [(6, 0, functional_tool_model_ids)]
dismantle_num = fields.Integer('拆解单数量', compute='_compute_dismantle_num', store=True) dismantle_num = fields.Integer('拆解单数量', compute='_compute_dismantle_num', tracking=True, store=True)
dismantle_ids = fields.One2many('sf.functional.tool.dismantle', 'functional_tool_id', '拆解单') dismantle_ids = fields.One2many('sf.functional.tool.dismantle', 'functional_tool_id', '拆解单')
@api.depends('dismantle_ids') @api.depends('dismantle_ids')

View File

@@ -107,11 +107,17 @@ class SfMaintenanceEquipment(models.Model):
if functional_tool_id.current_location != '机内刀库': if functional_tool_id.current_location != '机内刀库':
# 对功能刀具进行移动到生产线 # 对功能刀具进行移动到生产线
functional_tool_id.tool_inventory_displacement_out() functional_tool_id.tool_inventory_displacement_out()
functional_tool_id.write({ data_tool = {
'max_lifetime_value': data['MaxLife'], 'max_lifetime_value': data['MaxLife'],
'used_value': data['UseLife'], 'used_value': data['UseLife'],
'functional_tool_status': tool_install_time.get(data['State']) 'functional_tool_status': tool_install_time.get(data['State'])
}) }
if (functional_tool_id.functional_tool_status != '报警'
and tool_install_time.get(data['State']) == '报警'):
functional_tool_id.write(data_tool)
functional_tool_id.create_tool_dismantle()
else:
functional_tool_id.write(data_tool)
else: else:
logging.info('获取的【%s】设备不存在!!!' % data['DeviceId']) logging.info('获取的【%s】设备不存在!!!' % data['DeviceId'])
else: else:

View File

@@ -41,3 +41,6 @@ access_sf_functional_tool_dismantle_group_plan_dispatch,sf.functional.tool.disma
access_jikimo_bom,jikimo.bom,model_jikimo_bom,base.group_user,1,1,1,1 access_jikimo_bom,jikimo.bom,model_jikimo_bom,base.group_user,1,1,1,1
access_jikimo_bom_wizard,jikimo.bom.wizard,model_jikimo_bom_wizard,base.group_user,1,1,1,1 access_jikimo_bom_wizard,jikimo.bom.wizard,model_jikimo_bom_wizard,base.group_user,1,1,1,1
access_sf_functional_tool_dismantle_wiard,sf.functional.tool.dismantle.wiard,model_sf_functional_tool_dismantle_wiard,sf_base.group_sf_tool_user,1,1,1,0
access_sf_functional_tool_dismantle_wiard_group_plan_dispatch,sf.functional.tool.dismantle.wiard,model_sf_functional_tool_dismantle_wiard,sf_base.group_plan_dispatch,1,0,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
41
42
43
44
45
46

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

View File

@@ -1,17 +1,47 @@
.modal-content .o_cp_buttons { // .modal-content .o_cp_buttons {
display:none // display:none
} // }
.modal-content .o_control_panel { // .modal-content .o_control_panel {
display:none // display:none
} // }
.modal-content .o_list_button { // .modal-content .o_list_button {
// }
// .o_form_view .o_field_widget .o_list_renderer {
// width: 100%!important;
// margin:0 auto;
// overflow: auto;
// }
.o_field_widget.o_readonly_modifier.o_field_char.text-success[name=handle_freight_rfid],
.o_field_widget.o_readonly_modifier.o_field_many2one.text-success[name=integral_freight_barcode_id] {
a.text-success{
span {
color: #999;
}
}
} }
.custom_group:has(.text-success){
.o_form_view .o_field_widget .o_list_renderer { position: relative;
width: 100%!important; &::after{
margin:0 auto; content: '';
overflow: auto; display: block;
width: 72px;
height: 72px;
background: url('/sf_tool_management/static/images/replaceIcon.png') no-repeat center center;
background-size: 100%;
position: absolute;
bottom: 20px;
left: 300px;
}
}
.o_field_widget.o_readonly_modifier.o_field_char.text-success[name=handle_freight_rfid] {
display: flex;
align-items: center;
> span {
color: #999;
}
} }

View File

@@ -42,6 +42,7 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form create="0" edit="0" delete="0"> <form create="0" edit="0" delete="0">
<header> <header>
<button name="set_functional_tool_status" string="报警" type="object" invisible="1"/>
<!-- <button name="enroll_functional_tool_entity" string="功能刀具注册" type="object"--> <!-- <button name="enroll_functional_tool_entity" string="功能刀具注册" type="object"-->
<!-- class="btn-primary"/>--> <!-- class="btn-primary"/>-->
<field name="functional_tool_status" widget="statusbar" statusbar_visible="正常,报警,已拆除"/> <field name="functional_tool_status" widget="statusbar" statusbar_visible="正常,报警,已拆除"/>
@@ -192,6 +193,10 @@
</page> </page>
</notebook> </notebook>
</sheet> </sheet>
<div class="oe_chatter">
<field name="message_follower_ids"/>
<field name="message_ids"/>
</div>
</form> </form>
</field> </field>
</record> </record>

View File

@@ -531,10 +531,10 @@
<div> <div>
<separator string="刀柄:" style="font-size: 13px;"/> <separator string="刀柄:" style="font-size: 13px;"/>
</div> </div>
<group> <group class="custom_group" >
<field name="handle_code_id" string="序列号" placeholder="请选择" <field name="handle_code_id" string="序列号" placeholder="请选择"
options="{'no_create': True, 'no_quick_create': True}"/> options="{'no_create': True, 'no_quick_create': True}"/>
<field name="handle_freight_rfid" string="Rfid"/> <field name="handle_freight_rfid" string="Rfid" decoration-success="handle_freight_rfid"/>
<field name="handle_product_id" string="名称"/> <field name="handle_product_id" string="名称"/>
<field name="cutting_tool_cutterhandle_model_id" string="型号"/> <field name="cutting_tool_cutterhandle_model_id" string="型号"/>
<field name="handle_specification_id" string="规格"/> <field name="handle_specification_id" string="规格"/>
@@ -554,15 +554,15 @@
<separator string="整体式刀具:" style="font-size: 13px;"/> <separator string="整体式刀具:" style="font-size: 13px;"/>
</div> </div>
<group> <group>
<group> <group class="custom_group">
<field name="integral_freight_barcode_id" string="货位"/> <field name="integral_freight_barcode_id" string="货位" decoration-success="integral_verify == True"/>
<field name="integral_lot_id" string="批次"/> <field name="integral_lot_id" string="批次"/>
<field name="integral_product_id" string="名称"/> <field name="integral_product_id" string="名称"/>
<field name="cutting_tool_integral_model_id" string="型号"/> <field name="cutting_tool_integral_model_id" string="型号"/>
<field name="integral_specification_id" string="规格"/> <field name="integral_specification_id" string="规格"/>
<field name="sf_tool_brand_id_1" string="品牌"/> <field name="sf_tool_brand_id_1" string="品牌"/>
</group> </group>
<group> <group invisible="1">
<field name="integral_verify" string="" readonly="1"/> <field name="integral_verify" string="" readonly="1"/>
</group> </group>
</group> </group>
@@ -582,8 +582,8 @@
<separator string="刀片:" style="font-size: 13px;"/> <separator string="刀片:" style="font-size: 13px;"/>
</div> </div>
<group> <group>
<group> <group class="custom_group">
<field name="blade_freight_barcode_id" string="货位"/> <field name="blade_freight_barcode_id" string="货位" decoration-success="blade_verify == True"/>
<field name="blade_lot_id" string="批次"/> <field name="blade_lot_id" string="批次"/>
<field name="blade_product_id" string="名称"/> <field name="blade_product_id" string="名称"/>
<field name="cutting_tool_blade_model_id" string="型号"/> <field name="cutting_tool_blade_model_id" string="型号"/>
@@ -607,8 +607,8 @@
<separator string="刀杆:" style="font-size: 13px;"/> <separator string="刀杆:" style="font-size: 13px;"/>
</div> </div>
<group> <group>
<group> <group class="custom_group">
<field name="bar_freight_barcode_id" string="货位"/> <field name="bar_freight_barcode_id" string="货位" decoration-success="bar_verify == True"/>
<field name="bar_lot_id" string="批次"/> <field name="bar_lot_id" string="批次"/>
<field name="bar_product_id" string="名称"/> <field name="bar_product_id" string="名称"/>
<field name="cutting_tool_cutterbar_model_id" string="型号"/> <field name="cutting_tool_cutterbar_model_id" string="型号"/>
@@ -631,8 +631,8 @@
<separator string="刀盘:" style="font-size: 13px;"/> <separator string="刀盘:" style="font-size: 13px;"/>
</div> </div>
<group> <group>
<group> <group class="custom_group">
<field name="pad_freight_barcode_id" string="货位"/> <field name="pad_freight_barcode_id" string="货位" decoration-success="pad_verify == True"/>
<field name="pad_lot_id" string="批次"/> <field name="pad_lot_id" string="批次"/>
<field name="pad_product_id" string="名称"/> <field name="pad_product_id" string="名称"/>
<field name="cutting_tool_cutterpad_model_id" string="型号"/> <field name="cutting_tool_cutterpad_model_id" string="型号"/>

View File

@@ -770,3 +770,12 @@ class FunctionalToolAssemblyOrder(models.TransientModel):
# } # }
class FunctionalToolDismantle(models.TransientModel):
_name = 'sf.functional.tool.dismantle.wiard'
_description = '功能刀具拆解二次确认'
functional_tool_dismantle_id = fields.Many2one('sf.functional.tool.dismantle', '拆解单')
def confirm(self):
self.functional_tool_dismantle_id.confirmation_disassembly()
return True

View File

@@ -444,4 +444,19 @@
<field name="view_id" ref="sf_functional_tool_assembly_order_form"/> <field name="view_id" ref="sf_functional_tool_assembly_order_form"/>
<field name="target">new</field> <field name="target">new</field>
</record> </record>
<record id="sf_functional_tool_dismantle_wiard_form" model="ir.ui.view">
<field name="name">刀具拆解</field>
<field name="model">sf.functional.tool.dismantle.wiard</field>
<field name="arch" type="xml">
<form>
<div>刀具寿命未到期,是否继续拆解?</div>
<footer>
<button string="确定" name="confirm" type="object" class="btn-primary"/>
<button string="取消" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</odoo> </odoo>

View File

@@ -6,10 +6,7 @@ import win32gui
import win32con import win32con
import logging import logging
import time import time
import re
# 配置日志记录
logging.basicConfig(filename='service.log', level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s')
app = FastAPI() app = FastAPI()
@@ -19,11 +16,11 @@ class FileUploadRequest(BaseModel):
# FTP 服务器配置信息 # FTP 服务器配置信息
ftp_host = '110.52.114.162' ftp_host = '47.119.33.43'
ftp_port = 10021 ftp_port = 21
ftp_user = 'ftpuser' ftp_user = 'ftpuser'
ftp_password = '123456' ftp_password = 'FTPftp123'
ftp_directory = '/home/ftp/ftp_root/ThreeTest/XT/Before/' ftp_directory = 'ThreeTest/XT/Before/'
def find_child_window(parent_hwnd, class_name): def find_child_window(parent_hwnd, class_name):
@@ -63,25 +60,95 @@ def find_child_window_by_title(parent_hwnd, title):
return child_hwnds return child_hwnds
# 获取 ComboBox 的句柄
def get_combobox_handle(parent_handle):
combo_handle = win32gui.FindWindowEx(parent_handle, 0, "ComboBox", None)
if combo_handle == 0:
raise Exception("ComboBox not found")
return combo_handle
# 获取 ComboBox 中的所有选项
def get_combobox_items(combo_handle):
count = win32gui.SendMessage(combo_handle, win32con.CB_GETCOUNT, 0, 0)
items = []
for i in range(count):
length = win32gui.SendMessage(combo_handle, win32con.CB_GETLBTEXTLEN, i, 0)
buffer = win32gui.PyMakeBuffer(length + 1)
win32gui.SendMessage(combo_handle, win32con.CB_GETLBTEXT, i, buffer)
byte_data = buffer.tobytes()
# 尝试多种编码方式
text = None
# 尝试多种编码方式包括utf-16le
for encoding in ['utf-16le', 'utf-8', 'gbk', 'latin-1']:
try:
decoded_text = byte_data.decode(encoding).rstrip('\x00')
# 如果解码后的文本看起来是合理的,就接受它
if any(char.isprintable() for char in decoded_text):
text = decoded_text
break
except UnicodeDecodeError:
continue
# 如果所有解码方式都失败,或者内容仍有大量乱码,显示为十六进制字符串
if text is None or not all(char.isprintable() or char.isspace() for char in text):
text = byte_data.hex()
items.append(text)
return items
# 获取当前选定项
def get_combobox_selected(combo_handle):
index = win32gui.SendMessage(combo_handle, win32con.CB_GETCURSEL, 0, 0)
if index == -1:
return None
length = win32gui.SendMessage(combo_handle, win32con.CB_GETLBTEXTLEN, index, 0)
buffer = win32gui.PyMakeBuffer(1024) # 调整缓冲区大小
win32gui.SendMessage(combo_handle, win32con.CB_GETLBTEXT, index, buffer)
# 尝试多种编码方式进行解码
for encoding in ['utf-16le', 'utf-8', 'latin-1']:
try:
# 解码
raw_text = buffer.tobytes().decode(encoding, errors='ignore').rstrip('\x00')
# 使用正则表达式查找 "数字 + 月" 格式的部分
match = re.search(r'\d+月', raw_text)
if match:
return match.group()
# 使用正则表达式提取有效字符(中文、数字、字母和常见标点)
filtered_text = re.findall(r'[\w\u4e00-\u9fa5]+', raw_text)
# 返回匹配到的第一个有效部分
if filtered_text:
return filtered_text[0].strip()
except UnicodeDecodeError:
continue # 尝试下一个编码方式
# 如果所有解码方式都失败,返回 None
return None
# 设置 ComboBox 的值
def set_combobox_value(combo_handle, value):
items = get_combobox_items(combo_handle)
try:
index = items.index(value)
win32gui.SendMessage(combo_handle, win32con.CB_SETCURSEL, index, 0)
except ValueError:
raise Exception("Value not found in ComboBox")
def set_path_and_save(filename): def set_path_and_save(filename):
parent_hwnd = win32gui.FindWindow(None, '另存为') parent_hwnd = win32gui.FindWindow(None, '保存Excel文件')
if parent_hwnd == 0: if parent_hwnd == 0:
raise HTTPException(status_code=404, detail="没有找到保存报告的窗口,请检查!") raise HTTPException(status_code=404, detail="没有找到保存报告的窗口,请检查!")
# 这里假设“地址:”是你需要的部分标题 combo_handle = get_combobox_handle(parent_hwnd)
address_hwnds = find_child_window_by_partial_title(parent_hwnd, "地址:") #logging.info(f"ComboBox Items: {get_combobox_items(combo_handle)}")
logging.info(f"Current Selected: {get_combobox_selected(combo_handle)}")
# 确保找到的窗口句柄有效 local_file_path = "C:\\RationalDMIS64\\Output\\" + get_combobox_selected(combo_handle) + "\\" + filename
if not address_hwnds:
raise HTTPException(status_code=404, detail="未找到地址框,请联系管理员!")
# 假设找到的第一个窗口是目标组件
address_hwnd = address_hwnds[0]
logging.info(f"找到地址框地址: {win32gui.GetWindowText(address_hwnd)}")
# 设置路径
local_file_path = os.path.join(win32gui.GetWindowText(address_hwnd).split(' ')[1], filename)
logging.info(f"设置路径: {local_file_path}") logging.info(f"设置路径: {local_file_path}")
path_hwnds = find_child_window(parent_hwnd, 'Edit') path_hwnds = find_child_window(parent_hwnd, 'Edit')
@@ -115,7 +182,6 @@ def wait_for_file_to_save(filepath, timeout=30):
def upload_file_to_ftp(local_file): def upload_file_to_ftp(local_file):
if not os.path.isfile(local_file): if not os.path.isfile(local_file):
raise HTTPException(status_code=204, detail="文件未找到") raise HTTPException(status_code=204, detail="文件未找到")
@@ -157,4 +223,4 @@ async def upload_file(request: FileUploadRequest):
if __name__ == "__main__": if __name__ == "__main__":
import uvicorn import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000) uvicorn.run(app, host="0.0.0.0", port=8999)