diff --git a/sf_base/models/basic_parameters_fixture.py b/sf_base/models/basic_parameters_fixture.py index e9469355..868114e8 100644 --- a/sf_base/models/basic_parameters_fixture.py +++ b/sf_base/models/basic_parameters_fixture.py @@ -13,7 +13,7 @@ class BasicParametersFixture(models.Model): diameter = fields.Float('直径(mm)', digits=(16, 2)) # '零点卡盘' 字段 - weight = fields.Float('重量(mm)', digits=(16, 2)) + weight = fields.Float('重量(kg)', digits=(16, 2)) orientation_dish_diameter = fields.Float('定位盘直径(mm)', digits=(16, 2)) clamping_diameter = fields.Float('装夹直径(mm)', digits=(16, 2)) clamping_num = fields.Selection([('1', '1'), ('2', '2'), ('4', '4'), ('6', '6'), ('8', '8')], string='装夹单元数') diff --git a/sf_base/views/fixture_view.xml b/sf_base/views/fixture_view.xml index 0e86738d..07dadd33 100644 --- a/sf_base/views/fixture_view.xml +++ b/sf_base/views/fixture_view.xml @@ -171,7 +171,7 @@ - + @@ -197,7 +197,7 @@ - + @@ -220,7 +220,7 @@ - + @@ -248,7 +248,7 @@ - + @@ -278,7 +278,7 @@ - + @@ -307,7 +307,7 @@ - + @@ -335,7 +335,7 @@ - + diff --git a/sf_bf_connect/models/process_status.py b/sf_bf_connect/models/process_status.py index 705aca4c..9c939edd 100644 --- a/sf_bf_connect/models/process_status.py +++ b/sf_bf_connect/models/process_status.py @@ -1,7 +1,8 @@ from datetime import datetime import logging import requests -from odoo import fields, models +from odoo.exceptions import UserError +from odoo import fields, models, _ _logger = logging.getLogger(__name__) @@ -14,6 +15,17 @@ class StatusChange(models.Model): def action_confirm(self): # 在原有方法执行前记录日志和执行其他操作 logging.info('函数已经执行=============') + server_product_none = [] + for order in self.order_line: + for item in order.product_template_id.model_process_parameters_ids: + if item.gain_way == '外协': + server_product = self.env['product.template'].search( + [('server_product_process_parameters_id', '=', item.id), + ('detailed_type', '=', 'service')]) + if not server_product: + server_product_none.append(item.name) + if server_product_none: + raise UserError(_("请先至【产品】中创建【表面工艺参数】为%s的服务产品", ", ".join(server_product_none))) # 使用super()来调用原始方法(在本例中为'sale.order'模型的'action_confirm'方法) res = super(StatusChange, self).action_confirm() @@ -202,12 +214,12 @@ class FinishStatusChange(models.Model): [('id', 'child_of', self.picking_type_id.warehouse_id.view_location_id.id), ('usage', '!=', 'supplier')]) if self.env['stock.move'].search([ - ('state', 'in', ['confirmed', 'partially_available', 'waiting', 'assigned']), - ('product_qty', '>', 0), - ('location_id', 'in', wh_location_ids), - ('move_orig_ids', '=', False), - ('picking_id', 'not in', self.ids), - ('product_id', 'in', lines.product_id.ids)], limit=1): + ('state', 'in', ['confirmed', 'partially_available', 'waiting', 'assigned']), + ('product_qty', '>', 0), + ('location_id', 'in', wh_location_ids), + ('move_orig_ids', '=', False), + ('picking_id', 'not in', self.ids), + ('product_id', 'in', lines.product_id.ids)], limit=1): action = self.action_view_reception_report() action['context'] = {'default_picking_ids': self.ids} return action diff --git a/sf_dlm/models/product_supplierinfo.py b/sf_dlm/models/product_supplierinfo.py index 05191b44..a46f4856 100644 --- a/sf_dlm/models/product_supplierinfo.py +++ b/sf_dlm/models/product_supplierinfo.py @@ -122,7 +122,7 @@ class ResMrpBomMo(models.Model): # 查bom的原材料 def get_raw_bom(self, product): raw_bom = self.env['product.product'].search( - [('categ_id.type', '=', '原材料'), ('materials_type_id', '=', product.materials_type_id.id)]) + [('categ_id.type', '=', '原材料'), ('materials_type_id', '=', product.materials_type_id.id)],limit=1) return raw_bom diff --git a/sf_manufacturing/controllers/controllers.py b/sf_manufacturing/controllers/controllers.py index babda164..f3597093 100644 --- a/sf_manufacturing/controllers/controllers.py +++ b/sf_manufacturing/controllers/controllers.py @@ -145,7 +145,7 @@ class Manufacturing_Connect(http.Controller): logging.info('get_qcCheck error:%s' % e) return json.JSONEncoder().encode(res) - @http.route('/AutoDeviceApi/FeedBackStart', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, + @http.route('/AutoDeviceApi/FeedBackStart', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def button_Work_START(self, **kw): """ @@ -193,7 +193,7 @@ class Manufacturing_Connect(http.Controller): logging.info('button_Work_START error:%s' % e) return json.JSONEncoder().encode(res) - @http.route('/AutoDeviceApi/FeedBackEnd', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, + @http.route('/AutoDeviceApi/FeedBackEnd', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def button_Work_End(self, **kw): """ @@ -244,7 +244,7 @@ class Manufacturing_Connect(http.Controller): logging.info('button_Work_End error:%s' % e) return json.JSONEncoder().encode(res) - @http.route('/AutoDeviceApi/PartQualityInspect', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, + @http.route('/AutoDeviceApi/PartQualityInspect', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def PartQualityInspect(self, **kw): """ @@ -290,7 +290,7 @@ class Manufacturing_Connect(http.Controller): logging.info('PartQualityInspect error:%s' % e) return json.JSONEncoder().encode(res) - @http.route('/AutoDeviceApi/CMMProgDolod', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, + @http.route('/AutoDeviceApi/CMMProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def CMMProgDolod(self, **kw): """ @@ -330,7 +330,7 @@ class Manufacturing_Connect(http.Controller): logging.info('CMMProgDolod error:%s' % e) return json.JSONEncoder().encode(res) - @http.route('/AutoDeviceApi/NCProgDolod', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, + @http.route('/AutoDeviceApi/NCProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def NCProgDolod(self, **kw): """ @@ -429,7 +429,7 @@ class Manufacturing_Connect(http.Controller): logging.info('LocationChange error:%s' % e) return json.JSONEncoder().encode(res) - @http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, + @http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def AGVToProduct(self, **kw): """ @@ -490,7 +490,7 @@ class Manufacturing_Connect(http.Controller): logging.info('AGVToProduct error:%s' % e) return json.JSONEncoder().encode(res) - @http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, + @http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") def AGVDownProduct(self, **kw): """ diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index 8dc02132..ff2fde3a 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -185,8 +185,8 @@ class MrpProduction(models.Model): production.workorder_ids): production.state = 'rework' # 如果制造订单的功能刀具为【无效刀】则制造订单状态改为返工 - if production.tool_state == '2': - production.state = 'rework' + # if production.tool_state == '2': + # production.state = 'rework' def action_check(self): """ @@ -905,7 +905,6 @@ class MrpProduction(models.Model): # production.write( # {'state': 'progress', 'programming_state': '已编程', 'is_rework': False}) # logging.info('返工含有已编程未下发的程序更新完成:%s' % production.name) - logging.info('更新程序完成:%s' % production.name) else: raise UserError(result['message']) diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index 8f045703..ed4786cd 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -131,7 +131,9 @@ class ResMrpWorkOrder(models.Model): is_subcontract = fields.Boolean(string='是否外协') surface_technics_parameters_id = fields.Many2one('sf.production.process.parameter', string="表面工艺可选参数") picking_ids = fields.Many2many('stock.picking', string='外协出入库单') + # purchase_id = fields.Many2one('purchase.order', string='外协采购单') surface_technics_picking_count = fields.Integer("外协出入库", compute='_compute_surface_technics_picking_ids') + surface_technics_purchase_count = fields.Integer("外协采购", compute='_compute_surface_technics_purchase_ids') @api.depends('name', 'production_id.name') def _compute_surface_technics_picking_ids(self): @@ -153,6 +155,43 @@ class ResMrpWorkOrder(models.Model): action['context'] = dict(self._context, default_origin=self.name) return action + @api.depends('state', 'production_id.name') + def _compute_surface_technics_purchase_ids(self): + for order in self: + if order.routing_type == '表面工艺': + production_programming = self.env['mrp.production'].search( + [('programming_no', '=', order.production_id.programming_no)], order='name asc') + production_list = [production.name for production in production_programming] + purchase = self.env['purchase.order'].search([('origin', '=', ','.join(production_list))]) + for line in purchase.order_line: + if line.product_id.server_product_process_parameters_id == order.surface_technics_parameters_id and line.product_qty == len( + production_programming): + # server_product = self.env['product.template'].search( + # [('server_product_process_parameters_id', '=', pp.id), + # ('detailed_type', '=', 'service')]) + # purchase_order_line = self.env['purchase.order.line'].search( + # [('product_id', '=', server_product.id), ('product_qty', '=', len(production_programming))]) + # if purchase_order_line: + order.surface_technics_purchase_count = len(purchase) + else: + order.surface_technics_purchase_count = 0 + + def action_view_surface_technics_purchase(self): + self.ensure_one() + production_programming = self.env['mrp.production'].search( + [('programming_no', '=', self.production_id.programming_no)], order='name asc') + production_list = [production.name for production in production_programming] + purchase_orders = self.env['purchase.order'].search([('origin', '=', ','.join(production_list))]) + result = { + "type": "ir.actions.act_window", + "res_model": "purchase.order", + "res_id": purchase_orders.id, + # "domain": [['id', 'in', self.purchase_id]], + "name": _("Purchase Orders"), + 'view_mode': 'form', + } + return result + supplier_id = fields.Many2one('res.partner', string='外协供应商') equipment_id = fields.Many2one('maintenance.equipment', string='加工设备', tracking=True) # 保存名称 @@ -885,7 +924,22 @@ class ResMrpWorkOrder(models.Model): # workorder.state = 'ready' if workorder.routing_type == '表面工艺' and workorder.state not in ['done', 'progress']: if unclamp_workorder: - workorder.state = 'ready' + if workorder.is_subcontract is False: + workorder.state = 'ready' + else: + production_programming = self.env['mrp.production'].search( + [('programming_no', '=', self.production_id.programming_no)], order='name asc') + production_list = [production.name for production in production_programming] + purchase_orders = self.env['purchase.order'].search( + [('origin', '=', ','.join(production_list))]) + for line in purchase_orders.order_line: + if line.product_id.server_product_process_parameters_id == workorder.surface_technics_parameters_id and line.product_qty == len( + production_programming): + if purchase_orders.state == 'purchase': + workorder.state = 'ready' + else: + workorder.state = 'waiting' + # else: # if workorder.state not in ['cancel', 'rework']: # workorder.state = 'rework' @@ -1110,28 +1164,13 @@ class ResMrpWorkOrder(models.Model): picking_out = record.env['stock.move.line'].search( [('picking_id', '=', record.picking_ids[0].id)]) logging.info('picking_out:%s' % picking_out.picking_id.name) - if picking_out: - order_line_ids = [] - logging.info('surface_technics_parameters_id:%s' % record.surface_technics_parameters_id.name) - server_product = self.env['product.template'].search( - [('server_product_process_parameters_id', '=', record.surface_technics_parameters_id.id), - ('detailed_type', '=', 'service')]) - logging.info('server_product:%s' % server_product.name) - if server_product: - order_line_ids.append((0, 0, { - 'product_id': server_product.product_variant_id.id, - 'product_qty': 1, - 'product_uom': server_product.uom_id.id - })) - self.env['purchase.order'].sudo().create({ - 'partner_id': server_product.seller_ids.partner_id.id, - 'origin': record.production_id.name, - 'state': 'draft', - 'order_line': order_line_ids, - }) - else: - raise UserError( - '请先在产品中配置表面工艺为%s相关的外协服务产品' % item.surface_technics_parameters_id.name) + # if picking_out: + # order_line_ids = [] + # logging.info('surface_technics_parameters_id:%s' % record.surface_technics_parameters_id.name) + # + # else: + # raise UserError( + # '请先在产品中配置表面工艺为%s相关的外协服务产品' % item.surface_technics_parameters_id.name) tem_date_planned_finished = record.date_planned_finished tem_date_finished = record.date_finished logging.info('routing_type:%s' % record.routing_type) diff --git a/sf_manufacturing/models/product_template.py b/sf_manufacturing/models/product_template.py index 4d05de81..41392409 100644 --- a/sf_manufacturing/models/product_template.py +++ b/sf_manufacturing/models/product_template.py @@ -5,8 +5,10 @@ import base64 import hashlib import os from odoo import models, fields, api, _ -from odoo.exceptions import ValidationError +from odoo.exceptions import ValidationError, UserError from odoo.modules import get_resource_path + + from OCC.Extend.DataExchange import read_step_file from OCC.Extend.DataExchange import write_stl_file @@ -106,6 +108,15 @@ class ResProductMo(models.Model): name = fields.Char('产品名称', compute='_compute_tool_name', store=True, required=False) + @api.constrains('seller_ids') + def _check_seller_ids(self): + if self.categ_type == '表面工艺': + if self.seller_ids: + if self.seller_ids[0].price == 0.0: + raise UserError("请在该产品【采购】中的【价格】进行输入") + else: + raise UserError("请在【采购】中输入供应商信息") + @api.depends('cutting_tool_model_id', 'specification_id') def _compute_tool_name(self): for item in self: @@ -113,6 +124,10 @@ class ResProductMo(models.Model): name = '%s%s' % (item.cutting_tool_model_id.name, item.specification_id.name) item.name = name + def _get_process_parameters_product(self, production_process): + return self.env['product.template'].search( + [('server_product_process_parameters_id', '=', production_process.id)]).seller_ids[0] + @api.onchange('cutting_tool_model_id') def _onchange_cutting_tool_model_id(self): for item in self: @@ -640,6 +655,10 @@ class ResProductMo(models.Model): 'part_number': item.get('part_number') or '', 'active': True, } + tax_id = self.env['account.tax'].sudo().search( + [('type_tax_use', '=', 'sale'), ('amount', '=', item.get('tax')), ('price_include', '=', 'True')]) + if tax_id: + vals.update({'taxes_id':[(6,0,[int(tax_id)])]}) copy_product_id.sudo().write(vals) product_id.product_tmpl_id.active = False return copy_product_id @@ -800,7 +819,7 @@ class ResProductFixture(models.Model): diameter = fields.Float('直径(mm)', digits=(16, 2)) # '零点卡盘' 字段 - weight = fields.Float('重量(mm)', digits=(16, 2)) + weight = fields.Float('重量(kg)', digits=(16, 2)) orientation_dish_diameter = fields.Float('定位盘直径(mm)', digits=(16, 2)) clamping_diameter = fields.Float('装夹直径(mm)', digits=(16, 2)) clamping_num = fields.Selection([('1', '1'), ('2', '2'), ('4', '4'), ('6', '6'), ('8', '8')], string='装夹单元数') @@ -947,6 +966,7 @@ class SfMaintenanceEquipmentAndProductTemplate(models.Model): raise ValidationError("机床基坐标获取失败") + class SfMaintenanceEquipmentTool(models.Model): _name = 'maintenance.equipment.tool' _description = '机床刀位' diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index d902997f..76248171 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -217,6 +217,23 @@ class StockRule(models.Model): ( p.move_dest_ids.procure_method != 'make_to_order' and not p.move_raw_ids and not p.workorder_ids)).action_confirm() + # 处理 根据制造订单生成的采购单坯料入库时到原材料库,手动将原材料位置该为坯料存货区 + for production in productions: + if production.picking_ids: + product_type_id = production.picking_ids[0].move_ids[0].product_id.categ_id + if product_type_id.name == '坯料': + location_id = self.env['stock.location'].search([('name', '=', '坯料存货区')]) + if not location_id: + logging.info(f'没有搜索到【坯料存货区】: {location_id}') + break + for picking_id in production.picking_ids: + if picking_id.picking_type_id.name == '内部调拨': + if picking_id.location_dest_id.product_type != product_type_id: + picking_id.location_dest_id = location_id.id + elif picking_id.picking_type_id.name == '生产发料': + if picking_id.location_id.product_type != product_type_id: + picking_id.location_id = location_id.id + for production in productions: ''' 创建制造订单时生成序列号 @@ -272,6 +289,61 @@ class StockRule(models.Model): product_id_to_production_names[product_id] = [production.name for production in all_production] for production_item in productions: if production_item.product_id.id in product_id_to_production_names: + if production_item.product_id.model_process_parameters_ids: + is_purchase = False + sorted_process_parameters = sorted(production_item.product_id.model_process_parameters_ids, + key=lambda w: w.id) + + consecutive_process_parameters = [] + m = 0 + for i in range(len(sorted_process_parameters) - 1): + if m == 0: + is_purchase = False + if self.env['product.template']._get_process_parameters_product( + sorted_process_parameters[i]).partner_id == self.env[ + 'product.template']._get_process_parameters_product(sorted_process_parameters[ + i + 1]).partner_id and \ + sorted_process_parameters[i].gain_way == '外协': + if sorted_process_parameters[i] not in consecutive_process_parameters: + consecutive_process_parameters.append(sorted_process_parameters[i]) + consecutive_process_parameters.append(sorted_process_parameters[i + 1]) + m += 1 + continue + else: + if m == len(consecutive_process_parameters) - 1 and m != 0: + self.env['purchase.order'].get_purchase_order(consecutive_process_parameters, + production_item, + product_id_to_production_names) + if sorted_process_parameters[i] in consecutive_process_parameters: + is_purchase = True + consecutive_process_parameters = [] + m = 0 + # 当前面的连续外协采购单生成再生成当前外协采购单 + if is_purchase is False: + self.env['purchase.order'].get_purchase_order(consecutive_process_parameters, + production_item, + product_id_to_production_names) + if m == len(consecutive_process_parameters) - 1 and m != 0: + self.env['purchase.order'].get_purchase_order(consecutive_process_parameters, + production_item, + product_id_to_production_names) + if sorted_process_parameters[i] in consecutive_process_parameters: + is_purchase = True + consecutive_process_parameters = [] + m = 0 + if m == len(consecutive_process_parameters) - 1 and m != 0: + self.env['purchase.order'].get_purchase_order(consecutive_process_parameters, + production_item, + product_id_to_production_names) + if is_purchase is False and m == 0: + if len(sorted_process_parameters) == 1: + self.env['purchase.order'].get_purchase_order(sorted_process_parameters, + production_item, + product_id_to_production_names) + else: + self.env['purchase.order'].get_purchase_order(sorted_process_parameters[i], + production_item, + product_id_to_production_names) # # 同一个产品多个制造订单对应一个编程单和模型库 # # 只调用一次fetchCNC,并将所有生产订单的名称作为字符串传递 if not production_item.programming_no: @@ -498,7 +570,7 @@ class StockPicking(models.Model): [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id), ('location_id', '=', self.env['stock.location'].search( [('barcode', 'ilike', 'VL-SPOC')]).id), - ('origin', '=', self.origin)]) + ('origin', '=', self.origin), ('picking_id', '=', self.id)]) if self.location_id == move_in.location_id and self.location_dest_id == move_in.location_dest_id: if move_out.origin == move_in.origin: if move_out.picking_id.state != 'done': diff --git a/sf_manufacturing/views/mrp_production_addional_change.xml b/sf_manufacturing/views/mrp_production_addional_change.xml index 3f44c961..69aed55e 100644 --- a/sf_manufacturing/views/mrp_production_addional_change.xml +++ b/sf_manufacturing/views/mrp_production_addional_change.xml @@ -122,7 +122,7 @@ groups="sf_base.group_sf_mrp_user"/> -