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/controllers/controllers.py b/sf_manufacturing/controllers/controllers.py index c85a559e..ddbf4048 100644 --- a/sf_manufacturing/controllers/controllers.py +++ b/sf_manufacturing/controllers/controllers.py @@ -215,7 +215,8 @@ class Manufacturing_Connect(http.Controller): if workorder.state != 'progress': res = {'Succeed': False, 'ErrorCode': 202, 'Error': '该工单未开始'} return json.JSONEncoder().encode(res) - workorder.write({'date_finished': datetime.now()}) + # workorder.write({'date_finished': datetime.now()}) + workorder.button_finish() # workorder.process_state = '待解除装夹' # workorder.sudo().production_id.process_state = '待解除装夹' diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index 4b9de955..0f428d43 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -293,7 +293,7 @@ class MrpProduction(models.Model): # 则根据设备找到工作中心;否则采用前面描述的工作中心分配机制; # 其他规则限制: 默认只分配给工作中心状态为非故障的工作中心; - def _create_workorder3(self, is_fetchcnc=False, scrap_production=False): + def _create_workorder3(self): # 根据product_id对self进行分组 grouped_product_ids = {k: list(g) for k, g in groupby(self, key=lambda x: x.product_id.id)} # 初始化一个字典来存储每个product_id对应的生产订单名称列表 @@ -336,12 +336,9 @@ class MrpProduction(models.Model): production_programming = self.search( [('product_id.id', '=', production.product_id.id), ('origin', '=', production.origin)], limit=1, order='id asc') - if not production_programming.programming_no or (is_fetchcnc is True and scrap_production): - # 制造订单报废/返工也需重新编程 - if (is_fetchcnc is True and scrap_production is False) or ( - is_fetchcnc is False and scrap_production is False): - production.fetchCNC(', '.join(product_id_to_production_names[production.product_id.id]), - scrap_production) + if not production_programming.programming_no: + production.fetchCNC(', '.join(product_id_to_production_names[production.product_id.id]), + scrap_production) else: production.write({'programming_no': production_programming.programming_no, 'programming_state': '编程中'}) @@ -409,12 +406,6 @@ class MrpProduction(models.Model): workorders_values.append( self.env['mrp.workorder'].json_workorder_str('', production, route)) production.workorder_ids = workorders_values - if production_programming.programming_state == '已编程': - logging.info("production_programming: %s" % production_programming.name) - production.workorder_ids.filtered(lambda t: t.routing_type == 'CNC加工').write({ - 'cnc_ids': production_programming.workorder_ids.filtered( - lambda - t1: t1.routing_type == 'CNC加工').cnc_ids}) for workorder in production.workorder_ids: workorder.duration_expected = workorder._get_duration_expected() @@ -557,8 +548,8 @@ class MrpProduction(models.Model): # work.button_finish() # 创建工单并进行排序 - def _create_workorder(self, is_fetchcnc=False, scrap_production=False): - self._create_workorder3(is_fetchcnc=is_fetchcnc, scrap_production=scrap_production) + def _create_workorder(self): + self._create_workorder3() self._reset_work_order_sequence() return True diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index 7eea98da..1d65c0ae 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -996,15 +996,15 @@ class ResMrpWorkOrder(models.Model): record.write({ 'date_planned_finished': tem_date_planned_finished # 保持原值 }) - if record.routing_type == 'CNC加工': - record.write({ - 'date_finished': tem_date_finished # 保持原值 - }) - if record.routing_type == 'CNC加工' and record.test_results in ['返工', '报废']: - record.production_id.action_cancel() - record.production_id.workorder_ids.write({'rfid_code': False, 'rfid_code_old': record.rfid_code}) - if record.is_remanufacture is True: - record.recreateManufacturingOrWorkerOrder() + # if record.routing_type == 'CNC加工': + # record.write({ + # 'date_finished': tem_date_finished # 保持原值 + # }) + # if record.routing_type == 'CNC加工' and record.test_results in ['返工', '报废']: + # record.production_id.action_cancel() + # record.production_id.workorder_ids.write({'rfid_code': False, 'rfid_code_old': record.rfid_code}) + # if record.is_remanufacture is True: + # record.recreateManufacturingOrWorkerOrder() is_production_id = True for workorder in record.production_id.workorder_ids: if workorder.state != 'done': 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_sale/models/sale_order.py b/sf_sale/models/sale_order.py index e168292b..9486f73c 100644 --- a/sf_sale/models/sale_order.py +++ b/sf_sale/models/sale_order.py @@ -233,12 +233,12 @@ class ResPartnerToSale(models.Model): if obj: raise UserError('该税ID已存在,请重新输入') - @api.constrains('email') - def _check_email(self): - if self.customer_rank > 0: - obj = self.sudo().search([('email', '=', self.email), ('id', '!=', self.id), ('active', '=', True)]) - if obj: - raise UserError('该邮箱已存在,请重新输入') + # @api.constrains('email') + # def _check_email(self): + # if self.customer_rank > 0: + # obj = self.sudo().search([('email', '=', self.email), ('id', '!=', self.id), ('active', '=', True)]) + # if obj: + # raise UserError('该邮箱已存在,请重新输入') @api.model def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): diff --git a/sf_sale/views/res_partner_view.xml b/sf_sale/views/res_partner_view.xml index cc8f49fb..a680bd7c 100644 --- a/sf_sale/views/res_partner_view.xml +++ b/sf_sale/views/res_partner_view.xml @@ -24,18 +24,18 @@ 1 {'readonly': [('id','!=', False)]}
- - - - - {'required': [('phone', '=', False)],'readonly': [('id','!=', False)]} - - - - {'required': [('mobile', '=', False)],'readonly': [('id','!=', False)]} - - + + + + + + + + + + + + {'readonly': [('id','!=', False)]} @@ -56,10 +56,10 @@ - - 1 - {'readonly': [('id','!=', False)]} - + + + + {'readonly': [('id','!=', False)]} @@ -101,7 +101,7 @@ + attrs="{'required' : [('supplier_rank','>', 0)]}"/> {'readonly': [('id','!=', False)]} @@ -195,10 +195,10 @@ {'invisible': [('customer_rank','=', 0)]} - - - + + + + diff --git a/sf_sale/views/sale_order_view.xml b/sf_sale/views/sale_order_view.xml index 0981ff59..647cf8ef 100644 --- a/sf_sale/views/sale_order_view.xml +++ b/sf_sale/views/sale_order_view.xml @@ -1,54 +1,6 @@ - - sale.order.search.inherit.sf - sale.order - primary - - - - - - - - - - - - - - - 报价 - ir.actions.act_window - sale.order - - tree,kanban,form,calendar,pivot,graph,activity - - {'search_default_my_quotation': 1} - -

- Create a new quotation, the first step of a new sale! -

-

- Once the quotation is confirmed by the customer, it becomes a sales order. -
- You will be able to create an invoice and collect the payment. -

-
-
- - - - - - - - sale.order.form.inherit.sf sale.order 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..bbcf9927 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) @@ -282,7 +283,8 @@ class CAMWorkOrderProgramKnifePlan(models.Model): 'loading_task_source': '0', 'applicant': self.env.user.name, - 'use_tool_time': self.need_knife_time, + 'use_tool_time': fields.Datetime.now() + timedelta( + hours=4) if not self.need_knife_time else self.need_knife_time, 'reason_for_applying': '工单用刀', 'sf_cam_work_order_program_knife_plan_id': self.id @@ -362,6 +364,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 +399,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 +416,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 +428,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 +445,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 +462,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 +479,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 +507,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 +517,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 +671,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 +732,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 +761,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 +769,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 +779,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 +790,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 +801,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 +811,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 +821,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 +831,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 +855,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 +884,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 +934,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 +1010,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..a945e965 100644 --- a/sf_tool_management/models/functional_tool.py +++ b/sf_tool_management/models/functional_tool.py @@ -14,14 +14,15 @@ class FunctionalCuttingToolEntity(models.Model): functional_tool_name_id = fields.Many2one('sf.functional.tool.assembly', string='功能刀具组装单', readonly=True) - tool_groups_id = fields.Many2one('sf.tool.groups', '刀具组', related='functional_tool_name_id.tool_groups_id') + tool_groups_id = fields.Many2one('sf.tool.groups', '刀具组', related='functional_tool_name_id.tool_groups_id', + store=True) code = fields.Char('编码') rfid = fields.Char('Rfid', readonly=True) rfid_dismantle = fields.Char('Rfid(已拆解)', readonly=True) 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) @@ -30,7 +31,7 @@ class FunctionalCuttingToolEntity(models.Model): coarse_middle_thin = fields.Selection([("1", "粗"), ('2', '中'), ('3', '精')], string='粗/中/精', readonly=True) new_former = fields.Selection([('0', '新'), ('1', '旧')], string='新/旧', readonly=True) tool_loading_length = fields.Float(string='总长度(mm)', readonly=True, digits=(10, 3)) - handle_length = fields.Float(string='刀柄长度(mm)',readonly=True, digits=(10, 3)) + handle_length = fields.Float(string='刀柄长度(mm)', readonly=True, digits=(10, 3)) functional_tool_length = fields.Float(string='伸出长(mm)', readonly=True, digits=(10, 3)) effective_length = fields.Float(string='有效长(mm)', readonly=True) tool_room_num = fields.Integer(string='刀具房数量', readonly=True) @@ -47,8 +48,6 @@ class FunctionalCuttingToolEntity(models.Model): string='位置', compute='_compute_current_location_id', store=True) image = fields.Binary('图片', readonly=True) - active = fields.Boolean(string='已归档', default=True) - safe_inventory_id = fields.Many2one('sf.real.time.distribution.of.functional.tools', string='功能刀具安全库存', readonly=True) @@ -318,6 +317,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 +331,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..c001ba1a 100644 --- a/sf_tool_management/views/functional_tool_views.xml +++ b/sf_tool_management/views/functional_tool_views.xml @@ -6,7 +6,7 @@ sf.functional.cutting.tool.entity.list.tree sf.functional.cutting.tool.entity - + @@ -19,16 +19,16 @@ - - - + + + + - - + @@ -46,8 +46,8 @@
- - + +
- + + + + @@ -343,12 +368,14 @@