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
This commit is contained in:
马广威
2025-04-14 17:24:04 +08:00
committed by Coding
3 changed files with 265 additions and 32 deletions

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

@@ -78,6 +78,10 @@
<p>公司邮箱: <span t-field="o.company_id.email"/></p> <p>公司邮箱: <span t-field="o.company_id.email"/></p>
</div> </div>
</div> </div>
<!-- <div style="border-top: 2px solid black;"></div> -->
<div class="text-center">
<span><span>1</span> 页/共 <span>1</span></span>
</div>
</div> </div>
</template> </template>
<template id="report_quality_inspection"> <template id="report_quality_inspection">