diff --git a/sf_dlm_management/views/product_template_management_view.xml b/sf_dlm_management/views/product_template_management_view.xml index 5361bcde..47febf25 100644 --- a/sf_dlm_management/views/product_template_management_view.xml +++ b/sf_dlm_management/views/product_template_management_view.xml @@ -32,9 +32,11 @@ options="{'no_create': True}" attrs="{'invisible': [('categ_type', '!=', '刀具')],'required': [('categ_type', '=', '刀具')],'readonly': [('id', '!=', False)]}" placeholder="请选择"/> + @@ -48,10 +50,10 @@ - - @@ -96,6 +98,16 @@ + + {'readonly': ['|',('id','!=',False),('categ_type', '=', + '刀具')], 'required': True} + + + + + + + @@ -456,7 +468,8 @@ - + diff --git a/sf_manufacturing/models/product_template.py b/sf_manufacturing/models/product_template.py index b7793896..0753d556 100644 --- a/sf_manufacturing/models/product_template.py +++ b/sf_manufacturing/models/product_template.py @@ -50,8 +50,8 @@ class ResProductMo(models.Model): cutting_tool_material_id = fields.Many2one('sf.cutting.tool.material', string='刀具物料') cutting_tool_type = fields.Char(string="刀具物料类型", related='cutting_tool_material_id.name', store=True) - cutting_tool_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='型号') - specification_id = fields.Many2one('sf.tool.materials.basic.parameters', string='规格') + cutting_tool_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='型号名称') + specification_id = fields.Many2one('sf.tool.materials.basic.parameters', string='物料号') cutting_tool_type_id = fields.Many2one('sf.cutting.tool.type', string='类型', domain="[('cutting_tool_material_id.name', '=', cutting_tool_type)]") @@ -104,6 +104,15 @@ class ResProductMo(models.Model): compaction_way_id = fields.Many2one('maintenance.equipment.image', '压紧方式', domain=[('type', '=', '压紧方式')]) + name = fields.Char('产品名称', compute='_compute_tool_name', store=True, required=False) + + @api.depends('cutting_tool_model_id', 'specification_id') + def _compute_tool_name(self): + for item in self: + if item.cutting_tool_model_id and item.specification_id: + name = '%s%s' % (item.cutting_tool_model_id.name, item.specification_id.name) + item.name = name + @api.onchange('cutting_tool_model_id') def _onchange_cutting_tool_model_id(self): for item in self: @@ -723,6 +732,10 @@ class ResProductMo(models.Model): logging.info('create-model_file:%s' % len(vals['model_file'])) self._sanitize_vals(vals) templates = super(ResProductMo, self).create(vals_list) + # 产品名称唯一性校验 + for item in templates: + if len(self.search([('name', '=', item.name)])) > 1: + raise ValidationError('产品名称【%s】已存在' % item.name) if "create_product_product" not in self._context: templates._create_variant_ids() diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index b5e1e732..03ec8d0e 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -3,6 +3,7 @@ import base64 import qrcode from collections import defaultdict, namedtuple import logging +import io import json from re import split as regex_split from re import findall as regex_findall @@ -668,12 +669,14 @@ class ReStockMove(models.Model): else: view = self.env.ref('stock.view_stock_move_nosuggest_operations') - if self.product_id.tracking == "serial" and self.state == "assigned": - print(self.origin) - if self.product_id.categ_id.name == '刀具': - self.next_serial = self._get_tool_next_serial(self.company_id, self.product_id, self.origin) - else: - self.next_serial = self.env['stock.lot']._get_next_serial(self.company_id, self.product_id) + if self.state == "assigned": + if self.product_id.tracking == "serial": + if self.product_id.categ_id.name == '刀具': + self.next_serial = self._get_tool_next_serial(self.company_id, self.product_id, self.origin) + else: + self.next_serial = self.env['stock.lot']._get_next_serial(self.company_id, self.product_id) + elif self.product_id.tracking == "lot": + self._put_tool_lot(self.company_id, self.product_id, self.origin) return { 'name': _('Detailed Operations'), @@ -698,6 +701,35 @@ class ReStockMove(models.Model): ), } + def _put_tool_lot(self, company, product, origin): + if product.tracking == "lot" and self.product_id.categ_id.name == '刀具': + if not self.move_line_nosuggest_ids: + lot_names = self.env['stock.lot'].generate_lot_names( + '%s-%s-%s' % ('%s-T-DJWL-%s' % ( + product.cutting_tool_model_id.code.split('-')[0], product.cutting_tool_material_id.code), + datetime.now().strftime("%Y%m%d"), origin), 1) + move_lines_commands = self._generate_serial_move_line_commands_tool_lot(lot_names) + self.write({'move_line_nosuggest_ids': move_lines_commands}) + for item in self.move_line_nosuggest_ids: + if item.lot_name: + item.lot_qr_code = self.compute_lot_qr_code(item.lot_name) + + def compute_lot_qr_code(self, lot_name): + qr = qrcode.QRCode( + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_L, + box_size=10, + border=4, + ) + qr.add_data(lot_name) + qr.make(fit=True) + img = qr.make_image(fill_color="black", back_color="white") + buffer = io.BytesIO() + img.save(buffer, format="PNG") + binary_data = buffer.getvalue() + data = base64.b64encode(binary_data).decode() # 确保返回的是字符串形式的数据 + return data + def _get_tool_next_serial(self, company, product, origin): """Return the next serial number to be attributed to the product.""" if product.tracking == "serial": @@ -711,6 +743,67 @@ class ReStockMove(models.Model): else: return "%s-T-%s-%s-%03d" % (split_codes[0], origin, product.specification_id.name, 1) + def _generate_serial_move_line_commands_tool_lot(self, lot_names, origin_move_line=None): + """Return a list of commands to update the move lines (write on + existing ones or create new ones). + Called when user want to create and assign multiple serial numbers in + one time (using the button/wizard or copy-paste a list in the field). + + :param lot_names: A list containing all serial number to assign. + :type lot_names: list + :param origin_move_line: A move line to duplicate the value from, default to None + :type origin_move_line: record of :class:`stock.move.line` + :return: A list of commands to create/update :class:`stock.move.line` + :rtype: list + """ + self.ensure_one() + + # Select the right move lines depending of the picking type configuration. + move_lines = self.env['stock.move.line'] + if self.picking_type_id.show_reserved: + move_lines = self.move_line_ids.filtered(lambda ml: not ml.lot_id and not ml.lot_name) + else: + move_lines = self.move_line_nosuggest_ids.filtered(lambda ml: not ml.lot_id and not ml.lot_name) + + loc_dest = origin_move_line and origin_move_line.location_dest_id + move_line_vals = { + 'picking_id': self.picking_id.id, + 'location_id': self.location_id.id, + 'product_id': self.product_id.id, + 'product_uom_id': self.product_id.uom_id.id, + 'qty_done': self.product_uom_qty, + } + if origin_move_line: + # `owner_id` and `package_id` are taken only in the case we create + # new move lines from an existing move line. Also, updates the + # `qty_done` because it could be usefull for products tracked by lot. + move_line_vals.update({ + 'owner_id': origin_move_line.owner_id.id, + 'package_id': origin_move_line.package_id.id, + 'qty_done': origin_move_line.qty_done or 1, + }) + + move_lines_commands = [] + qty_by_location = defaultdict(float) + for lot_name in lot_names: + # We write the lot name on an existing move line (if we have still one)... + if move_lines: + move_lines_commands.append((1, move_lines[0].id, { + 'lot_name': lot_name, + 'qty_done': 1, + })) + qty_by_location[move_lines[0].location_dest_id.id] += 1 + move_lines = move_lines[1:] + # ... or create a new move line with the serial name. + else: + loc = loc_dest or self.location_dest_id._get_putaway_strategy(self.product_id, quantity=1, + packaging=self.product_packaging_id, + additional_qty=qty_by_location) + move_line_cmd = dict(move_line_vals, lot_name=lot_name, location_dest_id=loc.id) + move_lines_commands.append((0, 0, move_line_cmd)) + qty_by_location[loc.id] += 1 + return move_lines_commands + class ReStockQuant(models.Model): _inherit = 'stock.quant' diff --git a/sf_mrs_connect/models/res_config_setting.py b/sf_mrs_connect/models/res_config_setting.py index 035b10a0..159e8d48 100644 --- a/sf_mrs_connect/models/res_config_setting.py +++ b/sf_mrs_connect/models/res_config_setting.py @@ -26,6 +26,7 @@ class ResConfigSettings(models.TransientModel): ftp_port = fields.Char(string='FTP端口') ftp_user = fields.Char(string='FTP用户') ftp_password = fields.Char(string='FTP密码') + enable_tool_presetter = fields.Boolean('是否启用刀具预调仪', default=True) def sf_all_sync(self): try: @@ -108,6 +109,7 @@ class ResConfigSettings(models.TransientModel): ftp_port = config.get_param('ftp_port', default='') ftp_user = config.get_param('ftp_user', default='') ftp_password = config.get_param('ftp_password', default='') + enable_tool_presetter = config.get_param('enable_tool_presetter', default='') values.update( token=token, @@ -121,7 +123,8 @@ class ResConfigSettings(models.TransientModel): ftp_host=ftp_host, ftp_port=ftp_port, ftp_user=ftp_user, - ftp_password=ftp_password + ftp_password=ftp_password, + enable_tool_presetter=enable_tool_presetter ) return values @@ -140,3 +143,4 @@ class ResConfigSettings(models.TransientModel): ir_config.set_param("ftp_port", self.ftp_port or "") ir_config.set_param("ftp_user", self.ftp_user or "") ir_config.set_param("ftp_password", self.ftp_password or "") + ir_config.set_param("enable_tool_presetter", self.enable_tool_presetter or False) diff --git a/sf_mrs_connect/views/res_config_settings_views.xml b/sf_mrs_connect/views/res_config_settings_views.xml index 2be92365..08e9560f 100644 --- a/sf_mrs_connect/views/res_config_settings_views.xml +++ b/sf_mrs_connect/views/res_config_settings_views.xml @@ -114,6 +114,21 @@ +
+

刀具预调仪配置

+
+
+
+ +
+
+
+
+
+
+
+
diff --git a/sf_tool_management/controllers/controllers.py b/sf_tool_management/controllers/controllers.py index 4b68e3f8..baba93fc 100644 --- a/sf_tool_management/controllers/controllers.py +++ b/sf_tool_management/controllers/controllers.py @@ -126,4 +126,4 @@ class Manufacturing_Connect(http.Controller): except Exception as e: res = {'Succeed': False, 'ErrorCode': 202, 'Error': e} logging.info('put_tool_preset_parameter_port error:%s' % e) - return json.JSONEncoder().encode(res) \ No newline at end of file + return json.JSONEncoder().encode(res) diff --git a/sf_tool_management/models/base.py b/sf_tool_management/models/base.py index 11ea4ac4..39c43d8e 100644 --- a/sf_tool_management/models/base.py +++ b/sf_tool_management/models/base.py @@ -181,6 +181,7 @@ class MachineTableToolChangingApply(models.Model): class CAMWorkOrderProgramKnifePlan(models.Model): _name = 'sf.cam.work.order.program.knife.plan' + _inherit = ['mail.thread'] _description = 'CAM工单程序用刀计划' name = fields.Char('工单任务编号') @@ -228,7 +229,7 @@ class CAMWorkOrderProgramKnifePlan(models.Model): estimated_processing_time = fields.Char('预计加工时间') plan_execute_status = fields.Selection([('0', '待下发'), ('1', '执行中'), ('2', '已完成')], - string='计划执行状态', default='0', readonly=False) + string='计划执行状态', default='0', readonly=False, tracking=True) sf_functional_tool_assembly_id = fields.Many2one('sf.functional.tool.assembly', '功能刀具组装', readonly=True) @@ -362,6 +363,7 @@ class CAMWorkOrderProgramKnifePlan(models.Model): class FunctionalToolAssembly(models.Model): _name = 'sf.functional.tool.assembly' + _inherit = ['mail.thread'] _description = '功能刀具组装' _order = 'assemble_status, use_tool_time asc' @@ -396,7 +398,7 @@ class FunctionalToolAssembly(models.Model): applicant = fields.Char(string='申请人', readonly=True) apply_time = fields.Datetime(string='申请时间', default=fields.Datetime.now(), readonly=True) assemble_status = fields.Selection([('0', '待组装'), ('1', '已组装')], string='组装状态', default='0', - readonly=True) + tracking=True, readonly=True) cutter_spacing_code_id = fields.Many2one('maintenance.equipment.tool', string='刀位号', readonly=True) whether_standard_knife = fields.Boolean(string='是否标准刀', default=True, readonly=True) reason_for_applying = fields.Char(string='申请原因', readonly=True) @@ -413,8 +415,9 @@ class FunctionalToolAssembly(models.Model): return categories.browse(functional_tool_type_ids) # 刀具物料信息 - # ==============整体式刀具型号============ - integral_freight_barcode = fields.Char('整体式刀具货位') + # ==============整体式刀具型号============= + integral_freight_barcode_id = fields.Many2one('sf.shelf.location', string='整体式刀具货位') + integral_lot_id = fields.Many2one('stock.lot', string='整体式刀具批次') integral_product_id = fields.Many2one('product.product', string='整体式刀具名称', compute='_compute_integral_product_id', store=True) cutting_tool_integral_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='整体式刀具型号', @@ -424,19 +427,15 @@ class FunctionalToolAssembly(models.Model): sf_tool_brand_id_1 = fields.Many2one('sf.machine.brand', string='整体式刀具品牌', related='integral_product_id.brand_id') - @api.depends('integral_freight_barcode') + @api.depends('integral_lot_id') def _compute_integral_product_id(self): for item in self: - if item.integral_freight_barcode: - location = self.env['sf.shelf.location'].sudo().search( - [('barcode', '=', item.integral_freight_barcode)]) - if location: - item.integral_product_id = location.product_id.id - else: - item.integral_product_id = False + if item.integral_lot_id: + item.integral_product_id = item.integral_lot_id.product_id.id # =================刀片型号============= - blade_freight_barcode = fields.Char('刀片货位') + blade_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀片货位') + blade_lot_id = fields.Many2one('stock.lot', string='刀片批次') blade_product_id = fields.Many2one('product.product', string='刀片名称', compute='_compute_blade_product_id', store=True) cutting_tool_blade_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀片型号', @@ -445,18 +444,15 @@ class FunctionalToolAssembly(models.Model): related='blade_product_id.specification_id') sf_tool_brand_id_2 = fields.Many2one('sf.machine.brand', '刀片品牌', related='blade_product_id.brand_id') - @api.depends('blade_freight_barcode') + @api.depends('blade_lot_id') def _compute_blade_product_id(self): for item in self: - if item.blade_freight_barcode: - location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', item.blade_freight_barcode)]) - if location: - item.blade_product_id = location.product_id.id - else: - item.blade_product_id = False + if item.blade_lot_id: + item.blade_product_id = item.blade_lot_id.product_id.id # ==============刀杆型号================ - bar_freight_barcode = fields.Char('刀杆货位') + bar_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀杆货位') + bar_lot_id = fields.Many2one('stock.lot', string='刀杆批次') bar_product_id = fields.Many2one('product.product', string='刀杆名称', compute='_compute_bar_product_id', store=True) cutting_tool_cutterbar_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀杆型号', @@ -465,18 +461,15 @@ class FunctionalToolAssembly(models.Model): related='bar_product_id.specification_id') sf_tool_brand_id_3 = fields.Many2one('sf.machine.brand', '刀杆品牌', related='bar_product_id.brand_id') - @api.depends('bar_freight_barcode') + @api.depends('bar_lot_id') def _compute_bar_product_id(self): for item in self: - if item.bar_freight_barcode: - location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', item.bar_freight_barcode)]) - if location: - item.bar_product_id = location.product_id.id - else: - item.bar_product_id = False + if item.bar_lot_id: + item.bar_product_id = item.bar_lot_id.product_id.id # =============刀盘型号================ - pad_freight_barcode = fields.Char('刀盘货位') + pad_freight_barcode_id = fields.Many2one('sf.shelf.location', string='刀盘货位') + pad_lot_id = fields.Many2one('stock.lot', string='刀盘批次') pad_product_id = fields.Many2one('product.product', string='刀盘名称', compute='_compute_pad_product_id', store=True) cutting_tool_cutterpad_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀盘型号', @@ -485,15 +478,11 @@ class FunctionalToolAssembly(models.Model): related='pad_product_id.specification_id') sf_tool_brand_id_4 = fields.Many2one('sf.machine.brand', '刀盘品牌', related='pad_product_id.brand_id') - @api.depends('pad_freight_barcode') + @api.depends('pad_lot_id') def _compute_pad_product_id(self): for item in self: - if item.pad_freight_barcode: - location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', item.pad_freight_barcode)]) - if location: - item.pad_product_id = location.product_id.id - else: - item.pad_product_id = False + if item.pad_lot_id: + item.pad_product_id = item.pad_lot_id.product_id.id # ==============刀柄型号============== handle_freight_rfid = fields.Char('刀柄Rfid', compute='_compute_handle_product_id', store=True) @@ -517,7 +506,8 @@ class FunctionalToolAssembly(models.Model): item.handle_freight_rfid = False # ==============夹头型号============== - chuck_freight_barcode = fields.Char('夹头货位') + chuck_freight_barcode_id = fields.Many2one('sf.shelf.location', string='夹头货位') + chuck_lot_id = fields.Many2one('stock.lot', string='夹头批次') chuck_product_id = fields.Many2one('product.product', string='夹头名称', compute='_compute_chuck_product_id', store=True) cutting_tool_cutterhead_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='夹头型号', @@ -526,17 +516,18 @@ class FunctionalToolAssembly(models.Model): related='chuck_product_id.specification_id') sf_tool_brand_id_6 = fields.Many2one('sf.machine.brand', '夹头品牌', related='chuck_product_id.brand_id') - @api.depends('chuck_freight_barcode') + @api.depends('chuck_lot_id') def _compute_chuck_product_id(self): for item in self: - if item.chuck_freight_barcode: - location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', item.chuck_freight_barcode)]) - if location: - item.chuck_product_id = location.product_id.id - else: - item.chuck_product_id = False + if item.chuck_lot_id: + item.chuck_product_id = item.chuck_lot_id.product_id.id # ==================待删除字段================== + integral_freight_barcode = fields.Char('整体式刀具货位') + blade_freight_barcode = fields.Char('刀片货位') + bar_freight_barcode = fields.Char('刀杆货位') + pad_freight_barcode = fields.Char('刀盘货位') + chuck_freight_barcode = fields.Char('夹头货位') blade_name = fields.Char('') integral_name = fields.Char('') blade_code_id = fields.Many2one('stock.lot', '刀片序列号') @@ -679,7 +670,7 @@ class FunctionalToolAssembly(models.Model): class FunctionalToolDismantle(models.Model): _name = 'sf.functional.tool.dismantle' - _inherit = ["barcodes.barcode_events_mixin"] + _inherit = ["barcodes.barcode_events_mixin", 'mail.thread'] _description = '功能刀具拆解' def on_barcode_scanned(self, barcode): @@ -740,6 +731,22 @@ class FunctionalToolDismantle(models.Model): raise ValidationError('该功能刀具因为%s拆解,无需录入库位' % self.dismantle_cause) name = fields.Char('名称', related='functional_tool_id.name') + code = fields.Char('拆解单号', default=lambda self: self._get_code(), readonly=True) + + def _get_code(self): + """ + 自动生成拆解单编码 + """ + new_time = str(fields.Date.today()) + datetime = new_time[2:4] + new_time[5:7] + new_time[-2:] + functional_tool_dismantle = self.env['sf.functional.tool.dismantle'].sudo().search( + [('code', 'ilike', datetime)], limit=1, order="id desc") + if not functional_tool_dismantle: + num = "%03d" % 1 + else: + m = int(functional_tool_dismantle.code[-3:]) + 1 + num = "%03d" % m + return 'GNDJ-CJD-%s-%s' % (datetime, num) functional_tool_id = fields.Many2one('sf.functional.cutting.tool.entity', '功能刀具', required=True, domain=[('functional_tool_status', '!=', '已拆除')]) @@ -753,7 +760,7 @@ class FunctionalToolDismantle(models.Model): dismantle_cause = fields.Selection( [('寿命到期报废', '寿命到期报废'), ('崩刀报废', '崩刀报废'), ('更换为其他刀具', '更换为其他刀具'), - ('刀具需磨削', '刀具需磨削')], string='拆解原因', required=True) + ('刀具需磨削', '刀具需磨削')], string='拆解原因', required=True, tracking=True) dismantle_data = fields.Datetime('拆解日期', readonly=True) dismantle_person = fields.Char('拆解人', readonly=True) image = fields.Binary('图片', readonly=True) @@ -761,7 +768,7 @@ class FunctionalToolDismantle(models.Model): scrap_id = fields.Char('报废单号', readonly=True) grinding_id = fields.Char('磨削单号', readonly=True) - state = fields.Selection([('待拆解', '待拆解'), ('已拆解', '已拆解')], default='待拆解') + state = fields.Selection([('待拆解', '待拆解'), ('已拆解', '已拆解')], default='待拆解', tracking=True) active = fields.Boolean('有效', default=True) # 刀柄 @@ -771,7 +778,9 @@ class FunctionalToolDismantle(models.Model): related='handle_product_id.cutting_tool_model_id') handle_brand_id = fields.Many2one('sf.machine.brand', string='刀柄品牌', related='handle_product_id.brand_id') handle_rfid = fields.Char(string='刀柄Rfid', compute='_compute_functional_tool_num', store=True) - scrap_boolean = fields.Boolean(string='刀柄是否报废', default=False) + handle_lot_id = fields.Many2one('stock.lot', string='刀柄序列号', compute='_compute_functional_tool_num', + store=True) + scrap_boolean = fields.Boolean(string='刀柄是否报废', default=False, tracking=True) # 整体式 integral_product_id = fields.Many2one('product.product', string='整体式刀具', @@ -780,6 +789,8 @@ class FunctionalToolDismantle(models.Model): related='integral_product_id.cutting_tool_model_id') integral_brand_id = fields.Many2one('sf.machine.brand', string='整体式刀具品牌', related='integral_product_id.brand_id') + 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))]") @@ -789,6 +800,7 @@ class FunctionalToolDismantle(models.Model): blade_type_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀片型号', related='blade_product_id.cutting_tool_model_id') 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))]") @@ -798,6 +810,7 @@ class FunctionalToolDismantle(models.Model): bar_type_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀杆型号', related='bar_product_id.cutting_tool_model_id') 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))]") @@ -807,6 +820,7 @@ class FunctionalToolDismantle(models.Model): pad_type_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀盘型号', related='pad_product_id.cutting_tool_model_id') 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))]") @@ -816,6 +830,7 @@ class FunctionalToolDismantle(models.Model): chuck_type_id = fields.Many2one('sf.cutting_tool.standard.library', string='夹头型号', related='chuck_product_id.cutting_tool_model_id') 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))]") @@ -839,12 +854,20 @@ class FunctionalToolDismantle(models.Model): item.rfid = item.functional_tool_id.rfid item.handle_rfid = item.functional_tool_id.rfid + # 产品 item.handle_product_id = item.functional_tool_id.functional_tool_name_id.handle_product_id.id item.integral_product_id = item.functional_tool_id.functional_tool_name_id.integral_product_id.id item.blade_product_id = item.functional_tool_id.functional_tool_name_id.blade_product_id.id item.bar_product_id = item.functional_tool_id.functional_tool_name_id.bar_product_id.id item.pad_product_id = item.functional_tool_id.functional_tool_name_id.pad_product_id.id item.chuck_product_id = item.functional_tool_id.functional_tool_name_id.chuck_product_id.id + # 批次/序列号 + item.handle_lot_id = item.functional_tool_id.functional_tool_name_id.handle_code_id.id + item.integral_lot_id = item.functional_tool_id.functional_tool_name_id.integral_lot_id.id + item.blade_lot_id = item.functional_tool_id.functional_tool_name_id.blade_lot_id.id + item.bar_lot_id = item.functional_tool_id.functional_tool_name_id.bar_lot_id.id + item.pad_lot_id = item.functional_tool_id.functional_tool_name_id.pad_lot_id.id + item.chuck_lot_id = item.functional_tool_id.functional_tool_name_id.chuck_lot_id.id else: item.tool_groups_id = False item.tool_type_id = False @@ -860,14 +883,45 @@ class FunctionalToolDismantle(models.Model): item.pad_product_id = False item.chuck_product_id = False + item.handle_lot_id = False + item.integral_lot_id = False + item.blade_lot_id = False + item.bar_lot_id = False + item.pad_lot_id = False + item.chuck_lot_id = False + + def location_duplicate_check(self): + """ + 目标货位去重校验 + """ + if self.blade_freight_id: + if self.bar_freight_id: + if self.blade_freight_id == self.bar_freight_id: + raise ValidationError('【刀片】和【刀杆】的目标货位重复,请重新选择!') + elif self.pad_freight_id: + if self.blade_freight_id == self.pad_freight_id: + raise ValidationError('【刀片】和【刀盘】的目标货位重复,请重新选择!') + if self.chuck_freight_id: + if self.chuck_freight_id == self.integral_freight_id: + raise ValidationError('【夹头】和【整体式刀具】的目标货位重复,请重新选择!') + if self.chuck_freight_id == self.blade_freight_id: + raise ValidationError('【夹头】和【刀片】的目标货位重复,请重新选择!') + if self.chuck_freight_id == self.bar_freight_id: + raise ValidationError('【夹头】和【刀杆】的目标货位重复,请重新选择!') + if self.chuck_freight_id == self.pad_freight_id: + raise ValidationError('【夹头】和【刀盘】的目标货位重复,请重新选择!') + def confirmation_disassembly(self): logging.info('%s刀具确认开始拆解' % self.dismantle_cause) + code = self.code if self.functional_tool_id.functional_tool_status == '已拆除': raise ValidationError('Rfid为【%s】的功能刀具已经拆解,请勿重复操作!' % self.functional_tool_id.rfid_dismantle) # 对拆解的功能刀具进行校验,只有在刀具房的功能刀具才能拆解 if self.functional_tool_id.tool_room_num == 0: raise ValidationError('Rfid为【%s】的功能刀具当前位置为【%s】,不能进行拆解!' % ( self.rfid, self.functional_tool_id.current_location)) + # 目标重复校验 + self.location_duplicate_check() location = self.env['stock.location'].search([('name', '=', '刀具组装位置')]) location_dest = self.env['stock.location'].search([('name', '=', '刀具房')]) # =================刀柄是否[报废]拆解======= @@ -879,53 +933,62 @@ class FunctionalToolDismantle(models.Model): functional_tool_assembly = self.functional_tool_id.functional_tool_name_id if self.scrap_boolean: # 刀柄报废 入库到Scrap - lot.create_stock_quant(location, location_dest_scrap, functional_tool_assembly.id, '功能刀具拆解', + lot.create_stock_quant(location, location_dest_scrap, functional_tool_assembly.id, code, functional_tool_assembly, functional_tool_assembly.tool_groups_id) else: # 刀柄不报废 入库到刀具房 - lot.create_stock_quant(location, location_dest, functional_tool_assembly.id, '功能刀具拆解', + lot.create_stock_quant(location, location_dest, functional_tool_assembly.id, code, functional_tool_assembly, functional_tool_assembly.tool_groups_id) # ==============功能刀具[报废]拆解================ if self.dismantle_cause in ['寿命到期报废', '崩刀报废']: # 除刀柄外物料报废 入库到Scrap if self.integral_product_id: - self.integral_product_id.dismantle_stock_moves(False, location, location_dest_scrap) + self.integral_product_id.dismantle_stock_moves(False, self.integral_lot_id, location, + location_dest_scrap, code) elif self.blade_product_id: - self.blade_product_id.dismantle_stock_moves(False, location, location_dest_scrap) + self.blade_product_id.dismantle_stock_moves(False, self.blade_lot_id, location, location_dest_scrap, + code) if self.bar_product_id: - self.bar_product_id.dismantle_stock_moves(False, location, location_dest_scrap) + self.bar_product_id.dismantle_stock_moves(False, self.bar_lot_id, location, location_dest_scrap, + code) elif self.pad_product_id: - self.pad_product_id.dismantle_stock_moves(False, location, location_dest_scrap) + self.pad_product_id.dismantle_stock_moves(False, self.pad_lot_id, location, location_dest_scrap, + code) if self.chuck_product_id: - self.chuck_product_id.dismantle_stock_moves(False, location, location_dest_scrap) + self.chuck_product_id.dismantle_stock_moves(False, self.chuck_lot_id, location, location_dest_scrap, + code) # ===========功能刀具[磨削]拆解============== - elif self.dismantle_cause in ['刀具需磨削']: - location_dest = self.env['stock.location'].search([('name', '=', '磨削房')]) - # 除刀柄外物料拆解 入库到具体库位 - if self.integral_product_id: - self.integral_product_id.dismantle_stock_moves(False, location, location_dest) - elif self.blade_product_id: - self.blade_product_id.dismantle_stock_moves(False, location, location_dest) - if self.bar_product_id: - self.bar_product_id.dismantle_stock_moves(False, location, location_dest) - elif self.pad_product_id: - self.pad_product_id.dismantle_stock_moves(False, location, location_dest) - if self.chuck_product_id: - self.chuck_product_id.dismantle_stock_moves(False, location, location_dest) - # ==============功能刀具[更换]拆解============== - elif self.dismantle_cause in ['更换为其他刀具']: - # 除刀柄外物料拆解 入库到具体库位 + # elif self.dismantle_cause in ['刀具需磨削']: + # location_dest = self.env['stock.location'].search([('name', '=', '磨削房')]) + # # 除刀柄外物料拆解 入库到具体库位 + # if self.integral_product_id: + # self.integral_product_id.dismantle_stock_moves(False, location, location_dest) + # elif self.blade_product_id: + # self.blade_product_id.dismantle_stock_moves(False, location, location_dest) + # if self.bar_product_id: + # self.bar_product_id.dismantle_stock_moves(False, location, location_dest) + # elif self.pad_product_id: + # self.pad_product_id.dismantle_stock_moves(False, location, location_dest) + # if self.chuck_product_id: + # self.chuck_product_id.dismantle_stock_moves(False, location, location_dest) + # ==============功能刀具[更换,磨削]拆解============== + elif self.dismantle_cause in ['更换为其他刀具', '刀具需磨削']: + # 除刀柄外物料拆解 入库到具体货位 if self.integral_freight_id: - self.integral_product_id.dismantle_stock_moves(self.integral_freight_id.barcode, location, - location_dest) + self.integral_product_id.dismantle_stock_moves(self.integral_freight_id, self.integral_lot_id, location, + location_dest, code) elif self.blade_freight_id: - self.blade_product_id.dismantle_stock_moves(self.blade_freight_id.barcode, location, location_dest) + self.blade_product_id.dismantle_stock_moves(self.blade_freight_id, self.blade_lot_id, location, + location_dest, code) if self.bar_freight_id: - self.bar_product_id.dismantle_stock_moves(self.bar_freight_id.barcode, location, location_dest) + self.bar_product_id.dismantle_stock_moves(self.bar_freight_id, self.bar_lot_id, location, + location_dest, code) elif self.pad_freight_id: - self.pad_product_id.dismantle_stock_moves(self.pad_freight_id.barcode, location, location_dest) + self.pad_product_id.dismantle_stock_moves(self.pad_freight_id, self.pad_lot_id, location, + location_dest, code) if self.chuck_freight_id: - self.chuck_product_id.dismantle_stock_moves(self.chuck_freight_id.barcode, location, location_dest) + self.chuck_product_id.dismantle_stock_moves(self.chuck_freight_id, self.chuck_lot_id, location, + location_dest, code) # ===============删除功能刀具的Rfid字段的值, 赋值给Rfid(已拆解)字段===== self.functional_tool_id.write({ 'rfid_dismantle': self.functional_tool_id.rfid, @@ -946,26 +1009,22 @@ class FunctionalToolDismantle(models.Model): class ProductProduct(models.Model): _inherit = 'product.product' - def dismantle_stock_moves(self, shelf_location_barcode, location_id, location_dest_id): + def dismantle_stock_moves(self, shelf_location_id, lot_id, location_id, location_dest_id, code): # 创建功能刀具拆解单产品库存移动记录 stock_move_id = self.env['stock.move'].sudo().create({ - 'name': '功能刀具拆解', + 'name': code, 'product_id': self.id, 'location_id': location_id.id, 'location_dest_id': location_dest_id.id, 'product_uom_qty': 1.00, 'state': 'done' }) - if shelf_location_barcode: - location = self.env['sf.shelf.location'].sudo().search([('barcode', '=', shelf_location_barcode)]) - location.product_num = location.product_num + 1 - else: - location = self.env['sf.shelf.location'] # 创建移动历史记录 stock_move_line_id = self.env['stock.move.line'].sudo().create({ 'product_id': self.id, + 'lot_id': lot_id.id, 'move_id': stock_move_id.id, - 'current_location_id': location.id, + 'destination_location_id': shelf_location_id.id, 'install_tool_time': fields.Datetime.now(), 'qty_done': 1.0, 'state': 'done', diff --git a/sf_tool_management/models/functional_tool.py b/sf_tool_management/models/functional_tool.py index 6f7e14bb..628f0b52 100644 --- a/sf_tool_management/models/functional_tool.py +++ b/sf_tool_management/models/functional_tool.py @@ -21,7 +21,7 @@ class FunctionalCuttingToolEntity(models.Model): name = fields.Char('名称') tool_name_id = fields.Many2one('sf.tool.inventory', '功能刀具名称') sf_cutting_tool_model_id = fields.Many2one('sf.cutting_tool.standard.library', string='刀具型号') - barcode_id = fields.Many2one('stock.lot', string='功能刀具序列号', readonly=True) + barcode_id = fields.Many2one('stock.lot', string='序列号', readonly=True) sf_cutting_tool_type_id = fields.Many2one('sf.functional.cutting.tool.model', string='功能刀具类型', group_expand='_read_group_mrs_cutting_tool_type_id', compute_sudo=True) @@ -318,6 +318,7 @@ class StockMoveLine(models.Model): class RealTimeDistributionOfFunctionalTools(models.Model): _name = 'sf.real.time.distribution.of.functional.tools' + _inherit = ['mail.thread'] _description = '功能刀具安全库存' name = fields.Char('名称', readonly=True, compute='_compute_name', store=True) @@ -331,11 +332,11 @@ class RealTimeDistributionOfFunctionalTools(models.Model): side_shelf_num = fields.Integer(string='线边刀库数量') on_tool_stock_num = fields.Integer(string='机内刀库数量') tool_stock_total = fields.Integer(string='当前库存量', readonly=True) - min_stock_num = fields.Integer('最低库存量') - max_stock_num = fields.Integer('最高库存量') + min_stock_num = fields.Integer('最低库存量', tracking=True) + max_stock_num = fields.Integer('最高库存量', tracking=True) batch_replenishment_num = fields.Integer('批次补货量', readonly=True, compute='_compute_batch_replenishment_num', store=True) - unit = fields.Char('单位') + unit = fields.Char('单位', default="件") image = fields.Binary('图片', readonly=False) coarse_middle_thin = fields.Selection([("1", "粗"), ('2', '中'), ('3', '精')], string='粗/中/精', readonly=False) diff --git a/sf_tool_management/views/functional_tool_views.xml b/sf_tool_management/views/functional_tool_views.xml index 76607a16..1dafacf3 100644 --- a/sf_tool_management/views/functional_tool_views.xml +++ b/sf_tool_management/views/functional_tool_views.xml @@ -86,7 +86,7 @@ - + - + @@ -411,6 +411,10 @@ +
+ + +
diff --git a/sf_tool_management/views/tool_base_views.xml b/sf_tool_management/views/tool_base_views.xml index b3e93238..050b4d21 100644 --- a/sf_tool_management/views/tool_base_views.xml +++ b/sf_tool_management/views/tool_base_views.xml @@ -376,6 +376,10 @@ +
+ + +
@@ -464,8 +468,8 @@
- - + +