diff --git a/jikimo_demand_plan_queue/__init__.py b/jikimo_demand_plan_queue/__init__.py new file mode 100644 index 00000000..a0fdc10f --- /dev/null +++ b/jikimo_demand_plan_queue/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models diff --git a/jikimo_demand_plan_queue/__manifest__.py b/jikimo_demand_plan_queue/__manifest__.py new file mode 100644 index 00000000..83f5e07d --- /dev/null +++ b/jikimo_demand_plan_queue/__manifest__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +{ + 'name': '机企猫 需求计划排程队列', + 'version': '1.0', + 'summary': """ 使用队列进行排程 """, + 'author': 'fox', + 'website': '', + 'category': '', + 'depends': ['queue_job_batch', 'sf_demand_plan'], + 'data': [ + + ], + + 'application': True, + 'installable': True, + 'auto_install': False, + 'license': 'LGPL-3', +} diff --git a/jikimo_demand_plan_queue/models/__init__.py b/jikimo_demand_plan_queue/models/__init__.py new file mode 100644 index 00000000..54314ee3 --- /dev/null +++ b/jikimo_demand_plan_queue/models/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import production_demand_plan diff --git a/jikimo_demand_plan_queue/models/production_demand_plan.py b/jikimo_demand_plan_queue/models/production_demand_plan.py new file mode 100644 index 00000000..fd51e055 --- /dev/null +++ b/jikimo_demand_plan_queue/models/production_demand_plan.py @@ -0,0 +1,20 @@ +from odoo import models, fields + + +class ProductionDemandPlan(models.Model): + _inherit = 'sf.production.demand.plan' + + + def _do_production_schedule(self, pro_plan_list): + """使用队列进行排程""" + batch_size = 10 + current_time = fields.Datetime.now().strftime('%Y%m%d%H%M%S') + index = 1 + for i in range(0, len(pro_plan_list), batch_size): + batch = self.env['queue.job.batch'].get_new_batch('plan-%s-%s' % (current_time, index)) + pro_plans = pro_plan_list[i:i+batch_size] + pro_plans.with_context( + job_batch=batch + ).with_delay().do_production_schedule() + index += 1 + batch.enqueue() \ No newline at end of file diff --git a/sf_demand_plan/__manifest__.py b/sf_demand_plan/__manifest__.py index 2a543f1a..aab4ed99 100644 --- a/sf_demand_plan/__manifest__.py +++ b/sf_demand_plan/__manifest__.py @@ -10,7 +10,7 @@ """, 'category': 'sf', 'website': 'https://www.sf.jikimo.com', - 'depends': ['sf_plan', 'jikimo_printing'], + 'depends': ['sf_plan'], 'data': [ 'security/ir.model.access.csv', 'views/demand_plan.xml', diff --git a/sf_demand_plan/models/sf_production_demand_plan.py b/sf_demand_plan/models/sf_production_demand_plan.py index 79fcb99b..12214bff 100644 --- a/sf_demand_plan/models/sf_production_demand_plan.py +++ b/sf_demand_plan/models/sf_production_demand_plan.py @@ -323,8 +323,12 @@ class SfProductionDemandPlan(models.Model): date_planned_start = datetime.combine(date_part, time_part) pro_plan_list.production_line_id = sf_production_line.id pro_plan_list.date_planned_start = date_planned_start - for pro_plan in pro_plan_list: - pro_plan.do_production_schedule() + self._do_production_schedule(pro_plan_list) + + def _do_production_schedule(self, pro_plan_list): + for pro_plan in pro_plan_list: + pro_plan.do_production_schedule() + def button_action_print(self): return { diff --git a/sf_demand_plan/static/src/js/print_demand.js b/sf_demand_plan/static/src/js/print_demand.js index 5df0d02f..a05638a7 100644 --- a/sf_demand_plan/static/src/js/print_demand.js +++ b/sf_demand_plan/static/src/js/print_demand.js @@ -25,7 +25,11 @@ odoo.define('sf_demand.print_demand', function (require) { ); } + if(!$('.denmand_set').length) { + + const checked = self.getParent().radioCheck || 'all' + self.$el.prepend(`
更多设置: @@ -33,21 +37,25 @@ odoo.define('sf_demand.print_demand', function (require) { - +
`) - self.$el.prepend(` - - `); - } + setTimeout(() => { + $(`input[name=set][value=${checked}]`).prop('checked', true) + $('.denmand_set').trigger('click') + }, 100); + self.$el.prepend(` + + `); + } }); }, start: function() { @@ -106,7 +114,7 @@ odoo.define('sf_demand.print_demand', function (require) { } }, getSelectedIds: function() { - return this.state.data.map(_ => { + return this.state.data.filter(_ => !_.hide).map(_ => { return _.data.id }) }, @@ -129,16 +137,24 @@ odoo.define('sf_demand.print_demand', function (require) { // self.do_warn("打印失败", error.data.message || "未知错误"); }).finally(e => { this.getParent().reload() - }) }, _onDenmandChange(e) { const isChecked = $(e.currentTarget).find('input:checked').val() + this.getParent().radioCheck = isChecked this.$el.find('tbody').find('.o_data_row').show() - if(!isChecked) return - this.$el.find('tbody').children().each(function() { + + this.state.data.forEach(_ => { + _.hide = false + }) + const self = this + if(!isChecked || isChecked == 'all') return + this.$el.find('tbody').children('.o_data_row').each(function() { if($(this).find('td[name=type]').text() != isChecked) { + const i = $(this).index() + + self.state.data[i].hide = true $(this).hide() } }) diff --git a/sf_dlm/models/product_template.py b/sf_dlm/models/product_template.py index a2d848c2..b7d54518 100644 --- a/sf_dlm/models/product_template.py +++ b/sf_dlm/models/product_template.py @@ -73,15 +73,20 @@ class ResProductTemplate(models.Model): copy_product_id.product_tmpl_id.active = True model_type = self.env['sf.model.type'].search([], limit=1) attachment = self.attachment_create(item['model_name'], item['model_data']) + # 判断参数中是否包含 坯料尺寸(长、宽、高) + blank_bool = any(value is not None and value != 0 for value in ( + item.get('blank_length'), item.get('blank_width'), item.get('blank_height'))) if all( + key in item for key in ('blank_length', 'blank_width', 'blank_height')) else False vals = { 'name': '%s-%s-%s' % ('P', order_id.name, i), 'blank_type': item.get('blank_type'), - 'model_long': item['model_long'] + model_type.embryo_tolerance, - 'model_width': item['model_width'] + model_type.embryo_tolerance, - 'model_height': item['model_height'] + model_type.embryo_tolerance, - 'model_volume': (item['model_long'] + model_type.embryo_tolerance) * ( - item['model_width'] + model_type.embryo_tolerance) * ( - item['model_height'] + model_type.embryo_tolerance), + 'model_long': item.get('blank_length') if blank_bool else item['model_long'] + model_type.embryo_tolerance, + 'model_width': item.get('blank_width') if blank_bool else item['model_width'] + model_type.embryo_tolerance, + 'model_height': item.get('blank_height') if blank_bool else item['model_height'] + model_type.embryo_tolerance, + 'model_volume': ((item['model_long'] + model_type.embryo_tolerance) * + (item['model_width'] + model_type.embryo_tolerance) * + (item['model_height'] + model_type.embryo_tolerance)) if not blank_bool else ( + item.get('blank_length') * item.get('blank_width') * item.get('blank_height')), 'product_model_type_id': model_type.id, 'model_processing_panel': 'R', 'model_machining_precision': item['model_machining_precision'], diff --git a/sf_machine_connect/controllers/controllers.py b/sf_machine_connect/controllers/controllers.py index cefa4ba8..4974127e 100644 --- a/sf_machine_connect/controllers/controllers.py +++ b/sf_machine_connect/controllers/controllers.py @@ -377,7 +377,11 @@ class Sf_Dashboard_Connect(http.Controller): line_list_obj = request.env['sf.production.line'].sudo().search([('name', 'ilike', 'CNC')]) line_list = list(map(lambda x: x.name, line_list_obj)) # print('line_list: %s' % line_list) - res['LineList'] = line_list + res['LineList'] = ['业绩总览'] + res['LineList'] += line_list + res['LineList'].append('人工线下加工中心') + # 增加“业绩总览”与“人工线下加工中心” + except Exception as e: res = {'Succeed': False, 'ErrorCode': 202, 'Error': e} @@ -401,37 +405,55 @@ class Sf_Dashboard_Connect(http.Controller): try: plan_obj = request.env['sf.production.plan'].sudo() - production_obj = request.env['mrp.production'].sudo() + # production_obj = request.env['mrp.production'].sudo() work_order_obj = request.env['mrp.workorder'].sudo() line_list = ast.literal_eval(kw['line_list']) + + line_list_obj = request.env['sf.production.line'].sudo().search([('name', 'ilike', 'CNC')]) + cnc_line_list = list(map(lambda x: x.name, line_list_obj)) # print('line_list: %s' % line_list) for line in line_list: + if line == '业绩总览': + work_order_domain = [('routing_type', 'in', ['人工线下加工', 'CNC加工'])] + plan_domain = [] + elif line == '人工线下加工中心': + work_order_domain = [('routing_type', '=', '人工线下加工')] + plan_domain = [('production_type', '=', '人工线下加工')] + else: + work_order_domain = [ + ('production_line_id.name', '=', line), + ('routing_type', '=', 'CNC加工') + ] + plan_domain = [('production_line_id.name', '=', line)] # # 工单计划量 # plan_data_total_counts = production_obj.search_count( # [('production_line_id.name', '=', line), ('state', 'not in', ['cancel']), # ('active', '=', True)]) # 工单计划量切换为CNC工单 - plan_data_total_counts = work_order_obj.search_count( - [('production_line_id.name', '=', line), ('id', '!=', 8061), - ('state', 'in', ['ready', 'progress', 'done']), ('routing_type', '=', 'CNC加工')]) + plan_data_total = work_order_obj.search(work_order_domain + [ + ('id', '!=', 8061), + ('state', 'in', ['ready', 'progress', 'done']) + ]) + + plan_data_total_counts = sum(plan_data_total.mapped('qty_production')) # # 工单完成量 # plan_data_finish_counts = plan_obj.search_count( # [('production_line_id.name', '=', line), ('state', 'in', ['finished'])]) # 工单完成量切换为CNC工单 - plan_data_finish_counts = work_order_obj.search_count( - [('production_line_id.name', '=', line), - ('state', 'in', ['done']), ('routing_type', '=', 'CNC加工')]) + plan_data_finish = work_order_obj.search(work_order_domain + [ + ('state', 'in', ['done']) + ]) + + plan_data_finish_counts = sum(plan_data_finish.mapped('qty_produced')) # 超期完成量 # 搜索所有已经完成的工单 - plan_data_overtime = work_order_obj.search([ - ('production_line_id.name', '=', line), - ('state', 'in', ['done']), - ('routing_type', '=', 'CNC加工') + plan_data_overtime = work_order_obj.search(work_order_domain + [ + ('state', 'in', ['done']) ]) # 使用 filtered 进行字段比较 @@ -440,36 +462,38 @@ class Sf_Dashboard_Connect(http.Controller): ) # 获取数量 - plan_data_overtime_counts = len(plan_data_overtime_counts) + # plan_data_overtime_counts = len(plan_data_overtime_counts) + plan_data_overtime_counts = sum(plan_data_overtime_counts.mapped('qty_produced')) # 查找符合条件的生产计划记录 - plan_data = plan_obj.search([ - ('production_line_id.name', '=', line), - ]) + # plan_data = plan_obj.search(plan_domain) # 过滤出那些检测结果状态为 '返工' 或 '报废' 的记录 # 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']) + faulty_plans = work_order_obj.search(work_order_domain + [ + ('state', 'in', ['scrap', 'rework']) ]) # 查找制造订单取消与归档的数量 - cancel_order_count = production_obj.search_count( - [('production_line_id.name', '=', line), ('state', 'in', ['cancel']), - ('active', '=', False)]) + # cancel_order_count = production_obj.search_count( + # [('production_line_id.name', '=', line), ('state', 'in', ['cancel']), + # ('active', '=', False)]) # 计算符合条件的记录数量 # plan_data_fault_counts = len(faulty_plans) + cancel_order_count - plan_data_fault_counts = len(faulty_plans) + # plan_data_fault_counts = len(faulty_plans) + plan_data_fault_counts = sum(faulty_plans.mapped('qty_produced')) # 工单返工数量 - plan_data_rework_counts = plan_obj.search_count( - [('production_line_id.name', '=', line), ('production_id.state', 'in', ['rework'])]) + plan_data_rework = work_order_obj.search(work_order_domain + [ + ('state', 'in', ['rework']) + ]) + + plan_data_rework_counts = sum(plan_data_rework.mapped('qty_produced')) # 工单完成率 finishe_rate = round( @@ -479,8 +503,9 @@ class Sf_Dashboard_Connect(http.Controller): plan_data_progress_deviation = plan_data_total_counts - plan_data_finish_counts - plan_data_fault_counts # 完成记录 - plan_data_finish_orders = plan_obj.search( - [('production_line_id.name', '=', line), ('state', 'in', ['finished'])]) + plan_data_finish_orders = plan_obj.search(plan_domain + [ + ('state', 'in', ['finished']) + ]) # # 检测量 # detection_nums = 0 @@ -534,25 +559,25 @@ class Sf_Dashboard_Connect(http.Controller): delay_rate = round((delay_num / plan_data_finish_counts if plan_data_finish_counts > 0 else 0), 3) on_time_rate = 1 - delay_rate - if plan_data: - data = { - 'plan_data_total_counts': plan_data_total_counts, - 'plan_data_finish_counts': plan_data_finish_counts, - 'plan_data_plan_counts': plan_data_total_counts, - 'plan_data_fault_counts': plan_data_fault_counts, - 'nopass_orders_counts': detection_data - len(pass_nums), - 'finishe_rate': finishe_rate, - 'plan_data_progress_deviation': plan_data_progress_deviation, - 'plan_data_rework_counts': plan_data_rework_counts, - 'on_time_rate': on_time_rate, - # 'detection_data': detection_data, - 'detection_data': plan_data_finish_counts, - 'pass_rate': (plan_data_finish_counts - plan_data_fault_counts) / plan_data_finish_counts, - 'plan_data_overtime_counts': plan_data_overtime_counts, - 'overtime_rate': plan_data_overtime_counts / plan_data_finish_counts - if plan_data_finish_counts > 0 else 0, - } - res['data'][line] = data + # if plan_data: + data = { + 'plan_data_total_counts': plan_data_total_counts, + 'plan_data_finish_counts': plan_data_finish_counts, + 'plan_data_plan_counts': plan_data_total_counts, + 'plan_data_fault_counts': plan_data_fault_counts, + 'nopass_orders_counts': detection_data - len(pass_nums), + 'finishe_rate': finishe_rate, + 'plan_data_progress_deviation': plan_data_progress_deviation, + 'plan_data_rework_counts': plan_data_rework_counts, + 'on_time_rate': on_time_rate, + # 'detection_data': detection_data, + 'detection_data': plan_data_finish_counts, + 'pass_rate': (plan_data_finish_counts - plan_data_fault_counts) / plan_data_finish_counts, + 'plan_data_overtime_counts': plan_data_overtime_counts, + 'overtime_rate': plan_data_overtime_counts / plan_data_finish_counts + if plan_data_finish_counts > 0 else 0, + } + res['data'][line] = data return json.dumps(res) # 注意使用 json.dumps 而不是直接用 json.JSONEncoder().encode() @@ -608,16 +633,34 @@ class Sf_Dashboard_Connect(http.Controller): date_list.append(current_date) current_date += timedelta(days=1) return date_list + - for line in line_list: - date_field_name = 'date_finished' # 替换为你模型中的实际字段名 - order_counts = [] + if time_unit == 'hour': + + for line in line_list: + date_field_name = 'date_finished' # 替换为你模型中的实际字段名 + order_counts = [] - if time_unit == 'hour': + if line == '业绩总览': + work_order_domain = [('routing_type', 'in', ['人工线下加工', 'CNC加工'])] + elif line == '人工线下加工中心': + work_order_domain = [('routing_type', '=', '人工线下加工')] + else: + work_order_domain = [ + ('production_line_id.name', '=', line), + ('routing_type', '=', 'CNC加工') + ] time_intervals = get_time_intervals(begin_time, end_time, time_unit) print('============================= %s' % time_intervals) time_count_dict = {} + plan_count_dict = {} + + orders = request.env['mrp.workorder'].sudo().search(work_order_domain + [ + ('state', 'in', ['done']), + (date_field_name, '>=', begin_time.strftime('%Y-%m-%d %H:%M:%S')), + (date_field_name, '<=', end_time.strftime('%Y-%m-%d %H:%M:%S')) + ]) for time_interval in time_intervals: start_time, end_time = time_interval @@ -629,66 +672,113 @@ class Sf_Dashboard_Connect(http.Controller): # (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')) - ]) + interval_orders = orders.filtered( + lambda o: o[date_field_name] >= start_time + and o[date_field_name] <= end_time + ) # 使用小时和分钟作为键,确保每个小时的数据有独立的键 key = start_time.strftime('%H:%M:%S') # 只取小时:分钟:秒作为键 - time_count_dict[key] = len(orders) + # time_count_dict[key] = len(orders) + time_count_dict[key] = sum(interval_orders.mapped('qty_produced')) + + # 计划量,目前只能从mail.message中筛选出 + plan_order_messages = request.env['mail.message'].sudo().search([ + ('model', '=', 'mrp.workorder'), + ('create_date', '>=', begin_time.strftime('%Y-%m-%d %H:%M:%S')), + ('create_date', '<=', end_time.strftime('%Y-%m-%d %H:%M:%S')), + ('tracking_value_ids.field_desc', '=', '状态'), + ('tracking_value_ids.new_value_char', '=', '就绪') + ]) + + for time_interval in time_intervals: + start_time, end_time = time_interval + + # 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')) # 包括结束时间 + # ]) + + interval_plan_orders = plan_order_messages.filtered( + lambda o: o.create_date >= start_time + and o.create_date <= end_time + ) + + interval_orders = request.env['mrp.workorder'].sudo().browse(interval_plan_orders.mapped('res_id')) + if line == '业绩总览': + interval_orders = interval_orders.filtered(lambda o: o.routing_type in ['人工线下加工', 'CNC加工']) + elif line == '人工线下加工中心': + interval_orders = interval_orders.filtered(lambda o: o.routing_type == '人工线下加工') + else: + interval_orders = interval_orders.filtered(lambda o: o.routing_type == 'CNC加工' and o.production_line_id.name == line) + + # 使用小时和分钟作为键,确保每个小时的数据有独立的键 + key = start_time.strftime('%H:%M:%S') # 只取小时:分钟:秒作为键 + # time_count_dict[key] = len(orders) + plan_count_dict[key] = sum(interval_orders.mapped('qty_production')) + # order_counts.append() res['data'][line] = { 'finish_order_nums': time_count_dict, - 'plan_order_nums': 28 + 'plan_order_nums': plan_count_dict } - return json.dumps(res) + else: - date_list = get_date_list(begin_time, end_time) + for line in line_list: + date_field_name = 'date_finished' # 替换为你模型中的实际字段名 + order_counts = [] - for date in date_list: - next_day = date + timedelta(days=1) - 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')) - ]) + if line == '业绩总览': + work_order_domain = [('routing_type', 'in', ['人工线下加工', 'CNC加工'])] + elif line == '人工线下加工中心': + work_order_domain = [('routing_type', '=', '人工线下加工')] + else: + work_order_domain = [ + ('production_line_id.name', '=', line), + ('routing_type', '=', 'CNC加工') + ] - 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')) - ]) - order_counts.append({ - 'date': date.strftime('%Y-%m-%d'), - 'order_count': len(orders), - 'rework_orders': len(rework_orders), - 'not_passed_orders': len(not_passed_orders) - }) - # 外面包一层,没什么是包一层不能解决的,包一层就能区分了,类似于包一层div - # 外面包一层的好处是,可以把多个数据结构打包在一起,方便前端处理 + date_list = get_date_list(begin_time, end_time) - # date_list_dict = {line: order_counts} + for date in date_list: + next_day = date + timedelta(days=1) + orders = request.env['mrp.workorder'].sudo().search(work_order_domain + [ + ('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')) + ]) - res['data'][line] = order_counts + rework_orders = request.env['mrp.workorder'].sudo().search(work_order_domain + [ + ('state', 'in', ['rework']), + (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(work_order_domain + [ + ('state', 'in', ['scrap', 'cancel']), + (date_field_name, '>=', date.strftime('%Y-%m-%d 00:00:00')), + (date_field_name, '<', next_day.strftime('%Y-%m-%d 00:00:00')) + ]) + order_counts.append({ + 'date': date.strftime('%Y-%m-%d'), + 'order_count': sum(orders.mapped('qty_produced')), + 'rework_orders': sum(rework_orders.mapped('qty_produced')), + 'not_passed_orders': sum(not_passed_orders.mapped('qty_produced')) + }) + # 外面包一层,没什么是包一层不能解决的,包一层就能区分了,类似于包一层div + # 外面包一层的好处是,可以把多个数据结构打包在一起,方便前端处理 + + # date_list_dict = {line: order_counts} + + res['data'][line] = order_counts return json.dumps(res) # 实时产量 @http.route('/api/RealTimeProduct', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*") def RealTimeProduct(self, **kw): """ - 获取实时产量 + 获取实时产量(作废) :param kw: :return: """ @@ -711,6 +801,21 @@ class Sf_Dashboard_Connect(http.Controller): # 当班计划量 for line in line_list: + + if line == '业绩总览': + work_order_domain = [('routing_type', 'in', ['人工线下加工', 'CNC加工'])] + plan_domain = [] + elif line == '人工线下加工中心': + work_order_domain = [('routing_type', '=', '人工线下加工')] + plan_domain = [('production_type', '=', '人工线下加工')] + else: + work_order_domain = [ + ('production_line_id.name', '=', line), + ('routing_type', '=', 'CNC加工') + ] + plan_domain = [('production_line_id.name', '=', line)] + + plan_order_nums = plan_obj.search_count( [('production_line_id.name', '=', line), ('state', 'not in', ['draft']), ('date_planned_start', '>=', begin_time), @@ -752,10 +857,10 @@ class Sf_Dashboard_Connect(http.Controller): :param kw: :return: """ - # res = {'status': 1, 'message': '成功', 'not_done_data': [], 'done_data': []} res = {'status': 1, 'message': '成功', 'data': {}} plan_obj = request.env['sf.production.plan'].sudo() + work_order_obj = request.env['mrp.workorder'].sudo() line_list = ast.literal_eval(kw['line_list']) begin_time_str = kw['begin_time'].strip('"') end_time_str = kw['end_time'].strip('"') @@ -765,28 +870,39 @@ class Sf_Dashboard_Connect(http.Controller): not_done_data = [] done_data = [] final_data = {} + not_done_index = 1 + done_index = 1 for line in line_list: + + if line == '业绩总览': + work_order_domain = [('routing_type', 'in', ['人工线下加工', 'CNC加工'])] + elif line == '人工线下加工中心': + work_order_domain = [('routing_type', '=', '人工线下加工')] + else: + work_order_domain = [ + ('production_line_id.name', '=', line), + ('routing_type', '=', 'CNC加工') + ] # 未完成订单 # 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加工') - ]) + not_done_orders = work_order_obj.search(work_order_domain + + [('state', 'in', ['ready', 'progress'])], order='id asc' + ) # 完成订单 # 获取当前时间,并计算24小时前的时间 current_time = datetime.now() time_24_hours_ago = current_time - timedelta(hours=24) - finish_orders = plan_obj.search([ - ('production_line_id.name', '=', line), ('state', 'in', ['finished']), - ('production_id.state', 'not in', ['cancel']), ('active', '=', True), - ('actual_end_time', '>=', time_24_hours_ago) - ]) + finish_orders = work_order_obj.search(work_order_domain + [ + ('state', 'in', ['finished']), + ('production_id.state', 'not in', ['cancel']), + ('date_finished', '>=', time_24_hours_ago) + ], order='id asc') # print(finish_orders) # 获取所有未完成订单的ID列表 @@ -795,14 +911,14 @@ class Sf_Dashboard_Connect(http.Controller): finish_order_ids = [order.id for order in finish_orders] # 对ID进行排序 - sorted_order_ids = sorted(order_ids) + # sorted_order_ids = sorted(order_ids) - finish_sorted_order_ids = sorted(finish_order_ids) + # finish_sorted_order_ids = sorted(finish_order_ids) # 创建ID与序号的对应关系 - id_to_sequence = {order_id: index + 1 for index, order_id in enumerate(sorted_order_ids)} + # id_to_sequence = {order_id: index + 1 for index, order_id in enumerate(sorted_order_ids)} - finish_id_to_sequence = {order_id: index + 1 for index, order_id in enumerate(finish_sorted_order_ids)} + # finish_id_to_sequence = {order_id: index + 1 for index, order_id in enumerate(finish_sorted_order_ids)} # # 输出结果或进一步处理 # for order_id, sequence in id_to_sequence.items(): @@ -833,16 +949,17 @@ class Sf_Dashboard_Connect(http.Controller): } line_dict = { - 'sequence': id_to_sequence[order.id], + 'sequence': not_done_index, 'workorder_name': order.production_id.name, 'blank_name': blank_name, 'material': material, 'dimensions': dimensions, - 'order_qty': 1, + 'order_qty': order.qty_production, 'state': state_dict[order.state], } not_done_data.append(line_dict) + not_done_index += 1 for finish_order in finish_orders: if not finish_order.actual_end_time: @@ -861,17 +978,18 @@ class Sf_Dashboard_Connect(http.Controller): material = material_match.group(1) if material_match else 'No match found' line_dict = { - 'sequence': finish_id_to_sequence[finish_order.id], + 'sequence': done_index, 'workorder_name': finish_order.name, 'blank_name': blank_name, 'material': material, 'dimensions': dimensions, - 'order_qty': finish_order.product_qty, + 'order_qty': order.qty_produced, '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_index += 1 # 开始包一层 res['data'][line] = {'not_done_data': not_done_data, 'done_data': done_data} diff --git a/sf_manufacturing/models/product_template.py b/sf_manufacturing/models/product_template.py index eec6b118..4062814e 100644 --- a/sf_manufacturing/models/product_template.py +++ b/sf_manufacturing/models/product_template.py @@ -894,15 +894,20 @@ class ResProductMo(models.Model): if not embryo_redundancy_id: raise UserError('请先配置模型类型内的坯料冗余') product_name = self.generate_product_name(order_id, item, i) + # 判断参数中是否包含 坯料尺寸(长、宽、高) + blank_bool = any(value is not None and value != 0 for value in ( + item.get('blank_length'), item.get('blank_width'), item.get('blank_height'))) if all( + key in item for key in ('blank_length', 'blank_width', 'blank_height')) else False vals = { 'name': product_name, 'blank_type': item.get('blank_type'), - 'model_long': self.format_float(item['model_long'] + embryo_redundancy_id.long), - 'model_width': self.format_float(item['model_width'] + embryo_redundancy_id.width), - 'model_height': self.format_float(item['model_height'] + embryo_redundancy_id.height), - 'model_volume': self.format_float((item['model_long'] + embryo_redundancy_id.long) * ( - item['model_width'] + embryo_redundancy_id.width) * ( - item['model_height'] + embryo_redundancy_id.height)), + 'model_long': item.get('blank_length') if blank_bool else self.format_float(item['model_long'] + embryo_redundancy_id.long), + 'model_width': item.get('blank_width') if blank_bool else self.format_float(item['model_width'] + embryo_redundancy_id.width), + 'model_height': item.get('blank_height') if blank_bool else self.format_float(item['model_height'] + embryo_redundancy_id.height), + 'model_volume': self.format_float(((item['model_long'] + embryo_redundancy_id.long) * + (item['model_width'] + embryo_redundancy_id.width) * + (item['model_height'] + embryo_redundancy_id.height))) if not blank_bool else ( + item.get('blank_length') * item.get('blank_width') * item.get('blank_height')), 'product_model_type_id': model_type.id, 'model_processing_panel': item['processing_panel_detail'], 'model_machining_precision': item['model_machining_precision'], diff --git a/sf_quality/data/insepection_report_template.xml b/sf_quality/data/insepection_report_template.xml index 14a42065..8359fe35 100644 --- a/sf_quality/data/insepection_report_template.xml +++ b/sf_quality/data/insepection_report_template.xml @@ -92,7 +92,7 @@
- + @@ -113,7 +113,7 @@
产品名称:
- +

检验结果

@@ -148,7 +148,7 @@
- +
@@ -182,7 +182,7 @@
- +

操作员:

@@ -200,11 +200,35 @@

--> - + + - - - + + + + @@ -275,7 +299,7 @@ - +
@@ -329,9 +353,11 @@
--> - - - + + + + + diff --git a/sf_quality/views/quality_check_view.xml b/sf_quality/views/quality_check_view.xml index aed8ce4d..3798ad95 100644 --- a/sf_quality/views/quality_check_view.xml +++ b/sf_quality/views/quality_check_view.xml @@ -66,7 +66,7 @@ 不合格 - {'invisible': ['|','|',('quality_state', '!=', 'pass'),('work_state','in', ('done', 'rework')),'&',('quality_state', '=', 'pass'), ('test_type', '=', '出厂检验报告')]} + {'invisible': ['|','|',('quality_state', '!=', 'pass'),('work_state','in', ('done', 'rework')),'&',('quality_state', '=', 'pass'), ('test_type', '=', 'factory_inspection')]} 不合格 diff --git a/sf_sale/models/sale_order.py b/sf_sale/models/sale_order.py index cab8368c..c879093e 100644 --- a/sf_sale/models/sale_order.py +++ b/sf_sale/models/sale_order.py @@ -292,7 +292,7 @@ class ResaleOrderLine(models.Model): manual_quotation = fields.Boolean('人工编程', default=False) model_url = fields.Char('模型文件地址') model_id = fields.Char('模型ID') - delivery_end_date = fields.Date('交货截止日期') + delivery_end_date = fields.Date('客户交期') @api.depends('embryo_redundancy_id') def _compute_is_incoming_material(self): diff --git a/sf_sale/views/sale_order_view.xml b/sf_sale/views/sale_order_view.xml index d45a5200..3b115620 100644 --- a/sf_sale/views/sale_order_view.xml +++ b/sf_sale/views/sale_order_view.xml @@ -141,7 +141,7 @@ hide - + diff --git a/sf_warehouse/migrations/1.2/post-migrate.py b/sf_warehouse/migrations/1.2/post-migrate.py index b681f796..3c2165e1 100644 --- a/sf_warehouse/migrations/1.2/post-migrate.py +++ b/sf_warehouse/migrations/1.2/post-migrate.py @@ -7,8 +7,14 @@ def migrate(cr, version): env = api.Environment(cr, SUPERUSER_ID, {}) sf_shelf_model = env["sf.shelf"] sf_shelf_location_model = env["sf.shelf.location"] + + preproduction_shelf_ids = sf_shelf_location_model.get_preproduction_shelf_ids() + shelves = sf_shelf_model.search([]) for shelf in shelves: + if shelf.id not in preproduction_shelf_ids: + continue + shelf_barcode = shelf.barcode or "" if not shelf_barcode: continue diff --git a/sf_warehouse/models/model.py b/sf_warehouse/models/model.py index c313e6de..2cb35264 100644 --- a/sf_warehouse/models/model.py +++ b/sf_warehouse/models/model.py @@ -471,7 +471,6 @@ class ShelfLocation(models.Model): record.display_rfid = record.product_sn_id.rfid if record.product_sn_id else '' except Exception as e: record.display_rfid = '' - _logger.error(f"计算 display_rfid 时出错: {e}") @api.depends('product_id') def _compute_tool(self): @@ -601,6 +600,24 @@ class ShelfLocation(models.Model): _layer_capacity = f"{_layer_capacity:02d}" record.kanban_show_layer_info=f"{_layer}-{_layer_capacity}" record.kanban_show_center_control_code=f"{_cc_code}" + @api.model + def get_preproduction_shelf_ids(self): + """ + 获取预生产区域的货架ID列表 + Returns: + list: 货架ID列表 + """ + query = """ + SELECT DISTINCT b.shelf_id + FROM stock_location a + LEFT JOIN sf_shelf_location b ON a.id = b.location_id + WHERE a.barcode LIKE 'WH-PREPRODUCTION' + """ + self.env.cr.execute(query) + result = self.env.cr.fetchall() + # 将结果转换为ID列表 + shelf_ids = [record[0] for record in result if record[0]] + return shelf_ids class SfShelfLocationLot(models.Model): _name = 'sf.shelf.location.lot' @@ -617,6 +634,7 @@ class SfShelfLocationLot(models.Model): for item in self: if item.qty_num > item.qty: raise ValidationError('变更数量不能比库存数量大!!!') + class SfStockMoveLine(models.Model): diff --git a/sf_warehouse/static/src/js/custom_kanban_controller.js b/sf_warehouse/static/src/js/custom_kanban_controller.js index ab3a57d6..2d591505 100644 --- a/sf_warehouse/static/src/js/custom_kanban_controller.js +++ b/sf_warehouse/static/src/js/custom_kanban_controller.js @@ -28,8 +28,18 @@ class CustomKanbanController extends KanbanController { isBaseStyle: true }); let self = this; - // 获取货架分层数据 + onWillStart(async () => { + try { + this.preproductionShelfIds = await this.orm.call( + 'sf.shelf.location', + 'get_preproduction_shelf_ids', + [] + ); + } catch (error) { + this.preproductionShelfIds = []; + } + this.searchModel.on('update', self, self._onUpdate); await this.loadShelfLayersData(); }); @@ -50,7 +60,11 @@ class CustomKanbanController extends KanbanController { let domain = this.searchModel.domain; if (domain.length > 0) { let shelfDomain = domain.find(item => item[0] === 'shelf_id'); - this.onShelfChange(shelfDomain[2]); + if (shelfDomain && shelfDomain[2] && this.preproductionShelfIds && this.preproductionShelfIds.includes(shelfDomain[2])) { + this.onShelfChange(shelfDomain[2]); + } else { + this.setKanbanStyle('sf_kanban_location_style'); + } } else { this.setKanbanStyle('sf_kanban_location_style'); } @@ -63,8 +77,7 @@ class CustomKanbanController extends KanbanController { let shelfDomain = domain.find(item => item[0] === 'shelf_id'); if (shelfDomain) { let shelfId = shelfDomain[2]; - // 如果货架ID存在,则设置相应的样式 - if (shelfId) { + if (shelfId && this.preproductionShelfIds.includes(shelfId)) { this.onShelfChange(shelfId); return; } @@ -75,7 +88,6 @@ class CustomKanbanController extends KanbanController { this.setKanbanStyle('sf_kanban_location_style'); } catch (error) { } - } // 加载所有货架的层数数据 @@ -107,10 +119,18 @@ class CustomKanbanController extends KanbanController { // 添加新类 if (isHave) kanbanViewEl.classList.add(style); } - const ghostCards = document.querySelectorAll('.o_kanban_ghost'); - ghostCards.forEach(card => { - card.remove(); - }); + + // 获取当前的搜索域 + let domain = this.searchModel.domain; + let shelfDomain = domain.find(item => item[0] === 'shelf_id'); + + // 只有当shelf_id在preproductionShelfIds中时才删除幽灵看板 + if (shelfDomain && this.preproductionShelfIds && this.preproductionShelfIds.includes(shelfDomain[2])) { + const ghostCards = document.querySelectorAll('.o_kanban_ghost'); + ghostCards.forEach(card => { + card.remove(); + }); + } } updatePagerLimit(limit) { diff --git a/sf_warehouse/views/shelf_location.xml b/sf_warehouse/views/shelf_location.xml index 8b7bf9b6..82548935 100644 --- a/sf_warehouse/views/shelf_location.xml +++ b/sf_warehouse/views/shelf_location.xml @@ -229,7 +229,7 @@ - +