diff --git a/quality_control/views/quality_views.xml b/quality_control/views/quality_views.xml index 38de9e4e..528003e3 100644 --- a/quality_control/views/quality_views.xml +++ b/quality_control/views/quality_views.xml @@ -267,7 +267,7 @@ - + diff --git a/sf_machine_connect/controllers/controllers.py b/sf_machine_connect/controllers/controllers.py index 4385937c..13394ddb 100644 --- a/sf_machine_connect/controllers/controllers.py +++ b/sf_machine_connect/controllers/controllers.py @@ -5,7 +5,7 @@ import json import base64 import logging import psycopg2 -from datetime import datetime, timedelta +from datetime import datetime, timedelta, timezone from odoo import http, fields from odoo.http import request @@ -414,7 +414,7 @@ class Sf_Dashboard_Connect(http.Controller): # 工单计划量切换为CNC工单 plan_data_total_counts = work_order_obj.search_count( - [('production_id.production_line_id.name', '=', line), + [('production_line_id.name', '=', line), ('id', '!=', 8061), ('state', 'in', ['ready', 'progress', 'done']), ('routing_type', '=', 'CNC加工')]) # # 工单完成量 @@ -423,13 +423,13 @@ class Sf_Dashboard_Connect(http.Controller): # 工单完成量切换为CNC工单 plan_data_finish_counts = work_order_obj.search_count( - [('production_id.production_line_id.name', '=', line), + [('production_line_id.name', '=', line), ('state', 'in', ['done']), ('routing_type', '=', 'CNC加工')]) # 超期完成量 # 搜索所有已经完成的工单 plan_data_overtime = work_order_obj.search([ - ('production_id.production_line_id.name', '=', line), + ('production_line_id.name', '=', line), ('state', 'in', ['done']), ('routing_type', '=', 'CNC加工') ]) @@ -448,9 +448,14 @@ class Sf_Dashboard_Connect(http.Controller): ]) # 过滤出那些检测结果状态为 '返工' 或 '报废' 的记录 - faulty_plans = plan_data.filtered(lambda p: any( - result.test_results in ['返工', '报废'] for result in p.production_id.detection_result_ids - )) + # faulty_plans = plan_data.filtered(lambda p: any( + # result.test_results in ['返工', '报废'] for result in p.production_id.detection_result_ids + # )) + + faulty_plans = request.env['quality.check'].sudo().search([ + ('operation_id.name', '=', 'CNC加工'), + ('quality_state', 'in', ['fail']) + ]) # 查找制造订单取消与归档的数量 cancel_order_count = production_obj.search_count( @@ -567,7 +572,7 @@ class Sf_Dashboard_Connect(http.Controller): """ res = {'status': 1, 'message': '成功', 'data': {}} # plan_obj = request.env['sf.production.plan'].sudo() - plan_obj = request.env['mrp.workorder'].sudo().search([('routing_type', '=', 'CNC加工')]) + # plan_obj = request.env['mrp.workorder'].sudo().search([('routing_type', '=', 'CNC加工')]) line_list = ast.literal_eval(kw['line_list']) begin_time_str = kw['begin_time'].strip('"') end_time_str = kw['end_time'].strip('"') @@ -617,11 +622,19 @@ class Sf_Dashboard_Connect(http.Controller): for time_interval in time_intervals: start_time, end_time = time_interval - orders = plan_obj.search([ - ('production_id.production_line_id.name', '=', line), + # orders = plan_obj.search([ + # ('production_line_id.name', '=', line), + # ('state', 'in', ['done']), + # (date_field_name, '>=', start_time.strftime('%Y-%m-%d %H:%M:%S')), + # (date_field_name, '<=', end_time.strftime('%Y-%m-%d %H:%M:%S')) # 包括结束时间 + # ]) + + orders = request.env['mrp.workorder'].sudo().search([ + ('routing_type', '=', 'CNC加工'), # 将第一个条件合并进来 + ('production_line_id.name', '=', line), ('state', 'in', ['done']), (date_field_name, '>=', start_time.strftime('%Y-%m-%d %H:%M:%S')), - (date_field_name, '<=', end_time.strftime('%Y-%m-%d %H:%M:%S')) # 包括结束时间 + (date_field_name, '<=', end_time.strftime('%Y-%m-%d %H:%M:%S')) ]) # 使用小时和分钟作为键,确保每个小时的数据有独立的键 @@ -638,18 +651,22 @@ class Sf_Dashboard_Connect(http.Controller): for date in date_list: next_day = date + timedelta(days=1) - orders = plan_obj.search([('production_id.production_line_id.name', '=', line), ('state', 'in', ['done']), - (date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')), - (date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00')) - ]) - - rework_orders = plan_obj.search( - [('production_id.production_line_id.name', '=', line), ('state', 'in', ['rework']), + orders = request.env['mrp.workorder'].sudo().search( + [('production_id.production_line_id.name', '=', line), ('state', 'in', ['done']), + ('routing_type', '=', 'CNC加工'), (date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')), (date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00')) ]) - not_passed_orders = plan_obj.search( + + rework_orders = request.env['mrp.workorder'].sudo().search( + [('production_id.production_line_id.name', '=', line), ('state', 'in', ['rework']), + ('routing_type', '=', 'CNC加工'), + (date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')), + (date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00')) + ]) + not_passed_orders = request.env['mrp.workorder'].sudo().search( [('production_id.production_line_id.name', '=', line), ('state', 'in', ['scrap', 'cancel']), + ('routing_type', '=', 'CNC加工'), (date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')), (date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00')) ]) @@ -751,11 +768,14 @@ class Sf_Dashboard_Connect(http.Controller): for line in line_list: # 未完成订单 - not_done_orders = plan_obj.search( - [('production_line_id.name', '=', line), ('state', 'not in', ['finished']), - ('production_id.state', 'not in', ['cancel', 'done']), ('active', '=', True) + # not_done_orders = plan_obj.search( + # [('production_line_id.name', '=', line), ('state', 'not in', ['finished']), + # ('production_id.state', 'not in', ['cancel', 'done']), ('active', '=', True) + # ]) + not_done_orders = request.env['mrp.workorder'].sudo().search( + [('production_line_id.name', '=', line), ('state', 'in', ['ready', 'progress']), + ('routing_type', '=', 'CNC加工') ]) - # print(not_done_orders) # 完成订单 # 获取当前时间,并计算24小时前的时间 @@ -807,16 +827,18 @@ class Sf_Dashboard_Connect(http.Controller): 'draft': '待排程', 'done': '已排程', 'processing': '生产中', - 'finished': '已完成' + 'finished': '已完成', + 'ready': '待加工', + 'progress': '生产中', } line_dict = { 'sequence': id_to_sequence[order.id], - 'workorder_name': order.name, + 'workorder_name': order.production_id.name, 'blank_name': blank_name, 'material': material, 'dimensions': dimensions, - 'order_qty': order.product_qty, + 'order_qty': 1, 'state': state_dict[order.state], } @@ -897,15 +919,17 @@ class Sf_Dashboard_Connect(http.Controller): cur.execute(sql2, (item,)) result2 = cur.fetchall() - # print('result2========', result2) - # + for row in result: res['data'][item] = {'idle_count': row[0]} alarm_count = [] for row in result2: alarm_count.append(row[1]) if row[0]: - total_alarm_time += abs(float(row[0])) + if float(row[0]) >= 28800: + continue + # total_alarm_time += abs(float(row[0])) + total_alarm_time += float(row[0]) else: total_alarm_time += 0.0 if len(list(set(alarm_count))) == 1: @@ -915,6 +939,7 @@ class Sf_Dashboard_Connect(http.Controller): alarm_count_num = 1 else: alarm_count_num = len(list(set(alarm_count))) + res['data'][item]['total_alarm_time'] = total_alarm_time / 3600 res['data'][item]['alarm_count_num'] = alarm_count_num @@ -1332,7 +1357,7 @@ class Sf_Dashboard_Connect(http.Controller): for result in results: alarm_last_24_nums.append(result[1]) if result[0]: - if float(result[0]) >= 1000: + if float(result[0]) >= 28800: continue alarm_last_24_time += float(result[0]) else: @@ -1350,7 +1375,7 @@ class Sf_Dashboard_Connect(http.Controller): for result in results: alarm_all_nums.append(result[1]) if result[0]: - if float(result[0]) >= 1000: + if float(result[0]) >= 28800: continue alarm_all_time += float(result[0]) else: @@ -1385,3 +1410,207 @@ class Sf_Dashboard_Connect(http.Controller): conn.close() return json.dumps(res) + + @http.route('/api/utilization/rate', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*") + def UtilizationRate(self, **kw): + """ + 获取稼动率 + """ + logging.info("kw=:%s" % kw) + res = {'status': 1, 'message': '成功', 'data': {}} + # 获取请求的机床数据 + machine_list = ast.literal_eval(kw['machine_list']) + line = kw['line'] + orders = request.env['mrp.workorder'].sudo().search([ + ('routing_type', '=', 'CNC加工'), # 将第一个条件合并进来 + ('production_line_id.name', '=', line), + ('state', 'in', ['done']) + ]) + + faulty_plans = request.env['quality.check'].sudo().search([ + ('operation_id.name', '=', 'CNC加工'), + ('quality_state', 'in', ['fail']) + ]) + + # 计算时间范围 + now = datetime.now() + today_start = now.replace(hour=0, minute=0, second=0, microsecond=0) + month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0) + + total_power_on_time = 0 + month_power_on_time = 0 + today_power_on_time = 0 + today_power_on_dict = {} + + today_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) + + + + + diff --git a/sf_quality/data/insepection_report_template.xml b/sf_quality/data/insepection_report_template.xml index d989137c..14a42065 100644 --- a/sf_quality/data/insepection_report_template.xml +++ b/sf_quality/data/insepection_report_template.xml @@ -78,6 +78,10 @@

公司邮箱:

+ +
+ 1 页/共 1 +