diff --git a/sf_machine_connect/views/default_delivery.xml b/sf_machine_connect/views/default_delivery.xml index 0238c736..c9f3c07c 100644 --- a/sf_machine_connect/views/default_delivery.xml +++ b/sf_machine_connect/views/default_delivery.xml @@ -11,6 +11,7 @@ + diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index b7c8fe91..f389b05b 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -27,6 +27,43 @@ class MrpProduction(models.Model): string='工单状态', default='未排') detection_result_ids = fields.One2many('sf.detection.result', 'production_id', '检测报告') + tool_state = fields.Selection([('0', '正常'), ('1', '缺刀'), ('2', '无效刀')], string='功能刀具状态', default='0', + store=True, compute='_compute_tool_state') + tool_state_remark = fields.Text(string='功能刀具状态备注(缺刀)', readonly=True) + tool_state_remark2 = fields.Text(string='功能刀具状态备注(无效刀)', readonly=True) + + @api.depends('workorder_ids.tool_state') + def _compute_tool_state(self): + for item in self: + if item: + workorder_ids = item.workorder_ids.filtered(lambda a: a.state not in ('rework', '返工')) + if workorder_ids.filtered(lambda a: a.tool_state == '2'): + item.tool_state = '2' + elif workorder_ids.filtered(lambda a: a.tool_state == '1'): + tool_state_remark = '' + data = {} + # 获取所有缺刀工单加工面对应缺的刀 + for work in workorder_ids.filtered(lambda a: a.tool_state == '1'): + if work.processing_panel not in list(data.keys()): + data.update({work.processing_panel: []}) + for cnc in work.cnc_ids.filtered(lambda a: a.tool_state == '1'): + if cnc.cutting_tool_name not in data[work.processing_panel]: + data[work.processing_panel].append(cnc.cutting_tool_name) + # 按格式生成缺刀提示信息 + for key in data: + if data.get(key) and not data.get(key): + if tool_state_remark != '': + tool_state_remark = f'{tool_state_remark}\n{key}缺刀:{data.get(key)}' + else: + tool_state_remark = f'{key}缺刀:{data.get(key)}' + item.tool_state = '1' + item.tool_state_remark = tool_state_remark + item.tool_state_remark2 = '' + else: + item.tool_state = '0' + item.tool_state_remark = '' + item.tool_state_remark2 = '' + # state = fields.Selection(selection_add=[ # ('pending_scheduling', '待排程'), # ('pending_processing', '待加工'), diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index a4b0527c..0522126e 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -13,7 +13,7 @@ from dateutil.relativedelta import relativedelta # import subprocess from odoo import api, fields, models, SUPERUSER_ID, _ from odoo.addons.sf_base.commons.common import Common -from odoo.exceptions import UserError +from odoo.exceptions import UserError,ValidationError from odoo.addons.sf_mrs_connect.models.ftp_operate import FtpController @@ -170,6 +170,21 @@ class ResMrpWorkOrder(models.Model): # 加工图纸 processing_drawing = fields.Binary(string='加工图纸') + # 功能刀具状态 + tool_state = fields.Selection([('0', '正常'), ('1', '缺刀'), ('2', '无效刀')], string='功能刀具状态', default='0', + store=True, compute='_compute_tool_state') + + @api.depends('cnc_ids.tool_state') + def _compute_tool_state(self): + for item in self: + if item: + if item.cnc_ids.filtered(lambda a: a.tool_state == '2'): + item.tool_state = '2' + elif item.cnc_ids.filtered(lambda a: a.tool_state == '1'): + item.tool_state = '1' + else: + item.tool_state = '0' + @api.depends('production_id') def _compute_save_name(self): """ @@ -999,6 +1014,8 @@ class ResMrpWorkOrder(models.Model): rfid_code = workorder.rfid_code workorder.write({'rfid_code_old': rfid_code, 'rfid_code': False}) + if workorder.rfid_code: + raise ValidationError(f'【{workorder.name}】工单解绑失败,请重新点击完成按钮!!!') # workorder.rfid_code_old = rfid_code # workorder.rfid_code = False logging.info('workorder.rfid_code:%s' % workorder.rfid_code) @@ -1095,6 +1112,8 @@ class CNCprocessing(models.Model): program_path = fields.Char('程序文件路径') program_create_date = fields.Datetime('程序创建日期') + tool_state = fields.Selection([('0', '正常'), ('1', '缺刀'), ('2', '无效刀')], string='刀具状态', default='0') + # mrs下发编程单创建CNC加工 def cnc_processing_create(self, cnc_workorder, ret, program_path, program_path_tmp): cnc_processing = None diff --git a/sf_manufacturing/views/mrp_production_addional_change.xml b/sf_manufacturing/views/mrp_production_addional_change.xml index 30f5491a..0e4d4391 100644 --- a/sf_manufacturing/views/mrp_production_addional_change.xml +++ b/sf_manufacturing/views/mrp_production_addional_change.xml @@ -99,6 +99,9 @@ + + + + @@ -513,6 +515,7 @@ + diff --git a/sf_tool_management/models/base.py b/sf_tool_management/models/base.py index b77e6469..5156a295 100644 --- a/sf_tool_management/models/base.py +++ b/sf_tool_management/models/base.py @@ -313,42 +313,27 @@ class CAMWorkOrderProgramKnifePlan(models.Model): 'applicant': None, 'sf_functional_tool_assembly_id': None}) - def create_cam_work_plan(self, cnc_processing_ids): + def create_cam_work_plan(self, cnc_processing): """ 根据传入的工单信息,查询是否有需要的功能刀具,如果没有则生成CAM工单程序用刀计划 """ - for cnc_processing in cnc_processing_ids: - status = False - if cnc_processing.cutting_tool_name: - functional_tools = self.env['sf.real.time.distribution.of.functional.tools'].sudo().search( - [('name', '=', cnc_processing.cutting_tool_name)]) - if functional_tools: - for functional_tool in functional_tools: - if functional_tool.on_tool_stock_num == 0: - if functional_tool.tool_stock_num == 0 and functional_tool.side_shelf_num == 0: - status = True - else: - status = True - if status: - knife_plan = self.env['sf.cam.work.order.program.knife.plan'].sudo().create({ - 'name': cnc_processing.workorder_id.production_id.name, - 'cam_procedure_code': cnc_processing.program_name, - 'filename': cnc_processing.cnc_id.name, - 'functional_tool_name': cnc_processing.cutting_tool_name, - 'cam_cutter_spacing_code': cnc_processing.cutting_tool_no, - 'process_type': cnc_processing.processing_type, - 'margin_x_y': float(cnc_processing.margin_x_y), - 'margin_z': float(cnc_processing.margin_z), - 'finish_depth': float(cnc_processing.depth_of_processing_z), - 'extension_length': float(cnc_processing.cutting_tool_extension_length), - 'shank_model': cnc_processing.cutting_tool_handle_type, - 'estimated_processing_time': cnc_processing.estimated_processing_time, - }) - logging.info('CAM工单程序用刀计划创建成功!!!') - # 创建装刀请求 - knife_plan.apply_for_tooling() - else: - logging.info('功能刀具【%s】满足CNC用刀需求!!!' % cnc_processing.cutting_tool_name) + knife_plan = self.env['sf.cam.work.order.program.knife.plan'].sudo().create({ + 'name': cnc_processing.workorder_id.production_id.name, + 'cam_procedure_code': cnc_processing.program_name, + 'filename': cnc_processing.cnc_id.name, + 'functional_tool_name': cnc_processing.cutting_tool_name, + 'cam_cutter_spacing_code': cnc_processing.cutting_tool_no, + 'process_type': cnc_processing.processing_type, + 'margin_x_y': float(cnc_processing.margin_x_y), + 'margin_z': float(cnc_processing.margin_z), + 'finish_depth': float(cnc_processing.depth_of_processing_z), + 'extension_length': float(cnc_processing.cutting_tool_extension_length), + 'shank_model': cnc_processing.cutting_tool_handle_type, + 'estimated_processing_time': cnc_processing.estimated_processing_time, + }) + logging.info('CAM工单程序用刀计划创建成功!!!') + # 创建装刀请求 + knife_plan.apply_for_tooling() def unlink_cam_plan(self, production): for item in production: @@ -834,7 +819,7 @@ class FunctionalToolDismantle(models.Model): integral_lot_id = fields.Many2one('stock.lot', string='整体式刀具批次', compute='_compute_functional_tool_num', store=True) integral_freight_id = fields.Many2one('sf.shelf.location', '整体式刀具目标货位', - domain="[('product_id', 'in', (integral_product_id, False))]") + domain="[('product_id', 'in', (integral_product_id, False)),('location_id.name', '=', '刀具房')]") # 刀片 blade_product_id = fields.Many2one('product.product', string='刀片', compute='_compute_functional_tool_num', @@ -844,7 +829,7 @@ class FunctionalToolDismantle(models.Model): blade_brand_id = fields.Many2one('sf.machine.brand', string='刀片品牌', related='blade_product_id.brand_id') blade_lot_id = fields.Many2one('stock.lot', string='刀片批次', compute='_compute_functional_tool_num', store=True) blade_freight_id = fields.Many2one('sf.shelf.location', '刀片目标货位', - domain="[('product_id', 'in', (blade_product_id, False))]") + domain="[('product_id', 'in', (blade_product_id, False)),('location_id.name', '=', '刀具房')]") # 刀杆 bar_product_id = fields.Many2one('product.product', string='刀杆', compute='_compute_functional_tool_num', @@ -854,7 +839,7 @@ class FunctionalToolDismantle(models.Model): bar_brand_id = fields.Many2one('sf.machine.brand', string='刀杆品牌', related='bar_product_id.brand_id') bar_lot_id = fields.Many2one('stock.lot', string='刀杆批次', compute='_compute_functional_tool_num', store=True) bar_freight_id = fields.Many2one('sf.shelf.location', '刀杆目标货位', - domain="[('product_id', 'in', (bar_product_id, False))]") + domain="[('product_id', 'in', (bar_product_id, False)),('location_id.name', '=', '刀具房')]") # 刀盘 pad_product_id = fields.Many2one('product.product', string='刀盘', compute='_compute_functional_tool_num', @@ -864,7 +849,7 @@ class FunctionalToolDismantle(models.Model): pad_brand_id = fields.Many2one('sf.machine.brand', string='刀盘品牌', related='pad_product_id.brand_id') pad_lot_id = fields.Many2one('stock.lot', string='刀盘批次', compute='_compute_functional_tool_num', store=True) pad_freight_id = fields.Many2one('sf.shelf.location', '刀盘目标货位', - domain="[('product_id', 'in', (pad_product_id, False))]") + domain="[('product_id', 'in', (pad_product_id, False)),('location_id.name', '=', '刀具房')]") # 夹头 chuck_product_id = fields.Many2one('product.product', string='夹头', compute='_compute_functional_tool_num', @@ -874,7 +859,7 @@ class FunctionalToolDismantle(models.Model): chuck_brand_id = fields.Many2one('sf.machine.brand', string='夹头品牌', related='chuck_product_id.brand_id') chuck_lot_id = fields.Many2one('stock.lot', string='夹头批次', compute='_compute_functional_tool_num', store=True) chuck_freight_id = fields.Many2one('sf.shelf.location', '夹头目标货位', - domain="[('product_id', 'in', (chuck_product_id, False))]") + domain="[('product_id', 'in', (chuck_product_id, False)),('location_id.name', '=', '刀具房')]") @api.onchange('functional_tool_id') def _onchange_freight(self): diff --git a/sf_tool_management/models/functional_tool.py b/sf_tool_management/models/functional_tool.py index 4e791fe5..1e496985 100644 --- a/sf_tool_management/models/functional_tool.py +++ b/sf_tool_management/models/functional_tool.py @@ -255,6 +255,16 @@ class FunctionalCuttingToolEntity(models.Model): result['domain'] = [('id', '=', self.safe_inventory_id.id)] return result + def cnc_function_tool_use_verify(self): + """ + cnc程序用刀可用校验(校验是否是制造订单所缺刀) + """ + if self.tool_name_id.name: + cnc_processing_ids = self.env['sf.cnc.processing'].search( + [('tool_state', '=', '1'), ('cutting_tool_name', '=', self.tool_name_id.name)]) + if cnc_processing_ids: + cnc_processing_ids.sudo().write({'tool_state': '0'}) + def tool_inventory_displacement_out(self): """ 机床当前刀库实时信息接口,功能刀具出库 @@ -265,6 +275,7 @@ class FunctionalCuttingToolEntity(models.Model): self.create_stock_move(stock_location_id, False) self.current_location_id = stock_location_id.id self.current_shelf_location_id = False + # self.barcode_id.create_stock_quant(location_inventory_id, stock_location_id, # self.functional_tool_name_id.id, '机床装刀', self.functional_tool_name_id, # self.functional_tool_name_id.tool_groups_id) diff --git a/sf_tool_management/models/mrp_workorder.py b/sf_tool_management/models/mrp_workorder.py index e21941ca..465d3ea6 100644 --- a/sf_tool_management/models/mrp_workorder.py +++ b/sf_tool_management/models/mrp_workorder.py @@ -29,13 +29,105 @@ class CNCprocessing(models.Model): # else: # raise ValidationError("MES装刀指令发送失败") + def cnc_tool_checkout(self, cnc_processing_ids): + """ + 根据传入的工单信息,查询是否有需要的功能刀具,如果没有则生成CAM工单程序用刀计划 + """ + cam_id = self.env['sf.cam.work.order.program.knife.plan'] + production_ids = [] # 制造订单集 + datas = {'缺刀': {}, '无效刀': {}} # 缺刀/无效刀集 + for cnc_processing in cnc_processing_ids: + # ======创建字典: {'缺刀': {'制造订单1': {'加工面1': [], ...}, ...}, '无效刀': {'制造订单1': {'加工面1': [], ...}, ...}}====== + production_name = cnc_processing.production_id.name # 制造订单 + processing_panel = cnc_processing.workorder_id.processing_panel # 加工面 + if production_name not in list(datas['缺刀'].keys()): + datas['缺刀'].update({production_name: {processing_panel: []}}) + datas['无效刀'].update({production_name: {processing_panel: []}}) + production_ids.append(cnc_processing.production_id) + else: + if processing_panel not in list[datas['缺刀'].get(production_name).keys()]: + datas['缺刀'].get(production_name).update({processing_panel: []}) + datas['无效刀'].get(production_name).update({processing_panel: []}) + # ====================================== + if cnc_processing.cutting_tool_name: + tool_name = cnc_processing.cutting_tool_name + # 检验CNC用刀是否是功能刀具清单中的刀具 + tool_inventory_id = self.env['sf.tool.inventory'].sudo().search([('name', '=', tool_name)]) + if not tool_inventory_id: + if cnc_processing.cutting_tool_name not in datas['无效刀'][production_name][processing_panel]: + datas['无效刀'][production_name][processing_panel].append(cnc_processing.cutting_tool_name) + cnc_processing.tool_state = '2' + logging.info(f'"无效刀":[{production_name}、{processing_panel}、{cnc_processing.cutting_tool_name}]') + # 跳过本次循环 + continue + # 校验CNC用刀在系统是否存在 + functional_tools = self.env['sf.functional.cutting.tool.entity'].sudo().search( + [('tool_name_id', '=', tool_inventory_id.id), ('functional_tool_status', '=', '正常')]) + # 判断线边、机内是否有满足条件的刀 + if not functional_tools.filtered(lambda p: p.current_location in ('线边刀库', '机内刀库')): + if cnc_processing.cutting_tool_name not in datas['缺刀'][production_name][processing_panel]: + datas['缺刀'][production_name][processing_panel].append(cnc_processing.cutting_tool_name) + cnc_processing.tool_state = '1' + logging.info(f'"缺刀":[{production_name}、{processing_panel}、{cnc_processing.cutting_tool_name}]') + # 判断是否有满足条件的刀 + if not functional_tools: + # 创建CAM申请装刀记录 + cam_id.create_cam_work_plan(cnc_processing) + logging.info('成功调用CAM工单程序用刀计划创建方法!!!') + logging.info(datas) + for production_id in production_ids: + if production_id: + data1 = datas['无效刀'].get(production_id.name) # data1: {'加工面1': [], ...} + data2 = datas['缺刀'].get(production_id.name) # data2: {'加工面1': [], ...} + tool_state_remark1 = '' + tool_state_remark2 = '' + # 对无效刀信息进行处理 + for key in data1: + if data1.get(key): + if tool_state_remark1 != '': + tool_state_remark1 = f'{tool_state_remark1}\n{key}无效刀:{data1.get(key)}' + else: + tool_state_remark1 = f'{key}无效刀:{data1.get(key)}' + # 无效刀处理逻辑 + # 1、创建制造订单无效刀检测结果记录 + production_id.detection_result_ids.create({ + 'production_id': production_id.id, + 'processing_panel': key, + 'routing_type': 'CNC加工', + 'rework_reason': 'programming', # 原因:编程(programming) + 'detailed_reason': '无效功能刀具', + 'test_results': '返工', + 'handle_result': '待处理' + }) + # 2、将制造订单状态改为返工 + production_id.write({ + 'state': 'rework' + }) + # 对缺刀信息进行处理 + if tool_state_remark1 == '': + for key in data2: + if data2.get(key) and not data1.get(key): + if tool_state_remark2 != '': + tool_state_remark2 = f'{tool_state_remark2}\n{key}缺刀:{data2.get(key)}' + else: + tool_state_remark2 = f'{key}缺刀:{data2.get(key)}' + # 将备注信息存入制造订单功能刀具状态的备注字段 + if production_id.tool_state_remark2 == '': + production_id.write({ + 'tool_state_remark': tool_state_remark2, + 'tool_state_remark2': tool_state_remark1 + }) + else: + production_id.write({ + 'tool_state_remark': tool_state_remark2, + 'tool_state_remark2': f'{production_id.tool_state_remark1}\n{tool_state_remark1}' + }) + @api.model_create_multi def create(self, vals): obj = super(CNCprocessing, self).create(vals) - for item in obj: - # 调用CAM工单程序用刀计划创建方法 - self.env['sf.cam.work.order.program.knife.plan'].create_cam_work_plan(item) - logging.info('成功调用CAM工单程序用刀计划创建方法!!!') + # 调用CAM工单程序用刀计划创建方法 + self.cnc_tool_checkout(obj) return obj diff --git a/sf_tool_management/models/stock.py b/sf_tool_management/models/stock.py index be0f2e45..a179c7d7 100644 --- a/sf_tool_management/models/stock.py +++ b/sf_tool_management/models/stock.py @@ -21,3 +21,40 @@ class ShelfLocation(models.Model): continue item.tool_rfid = '' item.tool_name_id = False + + +class StockMoveLine(models.Model): + _inherit = 'stock.move.line' + + @api.model_create_multi + def create(self, vals_list): + records = super(StockMoveLine, self).create(vals_list) + move_lines = records.filtered(lambda a: a.product_id.categ_id.name == '功能刀具' and a.state == 'done') + if move_lines: # 校验是否为功能刀具移动历史 + self.button_function_tool_use_verify(move_lines) + return records + + def button_function_tool_use_verify(self, move_lines): + """ + 对所有从【刀具房】到【制造前】的功能刀具进行校验(校验是否为制造订单所缺的刀) + """ + location_id = self.env['stock.location'].search([('name', '=', '刀具房')]) + location_dest_id = self.env['stock.location'].search([('name', '=', '制造前')]) + line_ids = move_lines.filtered( + lambda a: a.location_id == location_id and a.location_dest_id == location_dest_id) + for line_id in line_ids: + if line_id.lot_id: + self.env['sf.functional.cutting.tool.entity'].sudo().search( + [('barcode_id', '=', line_id.lot_id.id), + ('functional_tool_status', '=', '正常')]).cnc_function_tool_use_verify() + + +class StockPicking(models.Model): + _inherit = 'stock.picking' + + def button_validate(self): + res = super().button_validate() + move_lines = self.move_line_ids.filtered(lambda a: a.product_id.categ_id.name == '功能刀具') + if move_lines: + self.env['stock.move.line'].sudo().button_function_tool_use_verify(move_lines) + return res