diff --git a/jikimo_purchase_request/models/product_template.py b/jikimo_purchase_request/models/product_template.py index 623c0981..c4f2a589 100644 --- a/jikimo_purchase_request/models/product_template.py +++ b/jikimo_purchase_request/models/product_template.py @@ -13,7 +13,11 @@ class ProductTemplate(models.Model): template_id.purchase_request = product_id.purchase_request return template_id + +class ProdcutProduct(models.Model): + _inherit = 'product.product' + def copy_template(self, product_template_id): """ 复制成品模板时,复制采购申请 """ - super(ProductTemplate, self).copy_template(product_template_id) + super(ProdcutProduct, self).copy_template(product_template_id) self.purchase_request = product_template_id.purchase_request diff --git a/jikimo_purchase_request/models/purchase_request.py b/jikimo_purchase_request/models/purchase_request.py index 3ecd2161..9ed11f13 100644 --- a/jikimo_purchase_request/models/purchase_request.py +++ b/jikimo_purchase_request/models/purchase_request.py @@ -114,7 +114,10 @@ class PurchaseRequestLine(models.Model): def _compute_qty_to_buy(self): for pr in self: - qty_to_buy = sum(pr.mapped("product_qty")) - sum(pr.mapped("qty_done")) - sum(pr.mapped("qty_in_progress")) + qty_to_buy = sum(pr.mapped("product_qty")) + if pr.purchase_count > 0: + qty_to_buy -= sum(pr.mapped("purchase_lines").filtered(lambda po: po.state != 'cancel').mapped( + "product_qty")) pr.qty_to_buy = qty_to_buy > 0.0 pr.pending_qty_to_receive = qty_to_buy diff --git a/jikimo_sale_multiple_supply_methods/models/product_template.py b/jikimo_sale_multiple_supply_methods/models/product_template.py index cd730998..85190cab 100644 --- a/jikimo_sale_multiple_supply_methods/models/product_template.py +++ b/jikimo_sale_multiple_supply_methods/models/product_template.py @@ -6,6 +6,10 @@ class ProductTemplate(models.Model): is_manual_processing = fields.Boolean(string='人工线下加工') is_customer_provided = fields.Boolean(string='客供料') + +class ProductProduct(models.Model): + _inherit = 'product.product' + def copy_template(self, product_template_id): if not isinstance(product_template_id, ProductTemplate): raise ValueError('%s必须是ProductTemplate类型' % product_template_id) diff --git a/jikimo_workorder_exception/controllers/main.py b/jikimo_workorder_exception/controllers/main.py index cf208700..6134e27f 100644 --- a/jikimo_workorder_exception/controllers/main.py +++ b/jikimo_workorder_exception/controllers/main.py @@ -4,6 +4,7 @@ import json import logging from odoo.addons.sf_mrs_connect.controllers.controllers import Sf_Mrs_Connect from odoo.addons.sf_manufacturing.controllers.controllers import Manufacturing_Connect +from odoo.addons.sf_base.decorators.api_log import api_log from datetime import datetime _logger = logging.getLogger(__name__) @@ -12,6 +13,7 @@ class WorkorderExceptionConroller(http.Controller): @http.route('/AutoDeviceApi/BillError', type='json', auth='public', methods=['GET', 'POST'], csrf=False, cors="*") + @api_log('工单对接错误', requester='中控系统') def workder_exception(self, **kw): """ 记录工单异常 diff --git a/sf_base/commons/common.py b/sf_base/commons/common.py index 2dbb4795..579125f0 100644 --- a/sf_base/commons/common.py +++ b/sf_base/commons/common.py @@ -103,12 +103,19 @@ class PrintingUtils(models.AbstractModel): self.send_to_printer(host, port, zpl_code) - def add_qr_code_to_pdf(self, pdf_path:str, content:str, buttom_text:Optional[str]=False): + def add_qr_code_to_pdf( + self, + pdf_path:str, + content:str, + qr_code_buttom_text:Optional[str]=False, + buttom_text:Optional[str]=False, + ): """ 在PDF文件中添加二维码 :param pdf_path: PDF文件路径 :param content: 二维码内容 - :param buttom_text: 二维码下方文字 + :param qr_code_buttom_text: 二维码下方文字 + :param buttom_text: 正文下方文字 :return: 是否成功 """ if not os.path.exists(pdf_path): @@ -156,8 +163,9 @@ class PrintingUtils(models.AbstractModel): existing_pdf = PdfFileReader(original_file) output = PdfFileWriter() - # 处理第一页 - page = existing_pdf.getPage(0) + # 处理最后一页 + last_page = existing_pdf.getNumPages() - 1 + page = existing_pdf.getPage(last_page) # 获取页面尺寸 page_width = float(page.mediaBox.getWidth()) page_height = float(page.mediaBox.getHeight()) @@ -179,13 +187,21 @@ class PrintingUtils(models.AbstractModel): qr_y = margin + 20 # 将二维码向上移动一点,为文字留出空间 c.drawImage(qr_temp_path, page_width - qr_size - margin, qr_y, width=qr_size, height=qr_size) - if buttom_text: + if qr_code_buttom_text: # 在二维码下方绘制文字 - text = buttom_text + text = qr_code_buttom_text text_width = c.stringWidth(text, "SimSun" if font_found else "Helvetica", 10) # 准确计算文字宽度 text_x = page_width - qr_size - margin + (qr_size - text_width) / 2 # 文字居中对齐 text_y = margin + 20 # 文字位置靠近底部 c.drawString(text_x, text_y, text) + + if buttom_text: + # 在下方中间添加文字 + text = button_text + text_width = c.stringWidth(text, "SimSun" if font_found else "Helvetica", 10) # 准确计算文字宽度 + text_x = (page_width - text_width) / 2 # 文字居中对齐 + text_y = margin + 20 # 文字位置靠近底部 + c.drawString(text_x, text_y, text) c.save() @@ -196,11 +212,12 @@ class PrintingUtils(models.AbstractModel): # 合并原始页面和二维码页面 page.mergePage(qr_page) - output.addPage(page) # 添加剩余的页面 - for i in range(1, existing_pdf.getNumPages()): + for i in range(0, last_page): output.addPage(existing_pdf.getPage(i)) + + output.addPage(page) # 保存最终的PDF到一个临时文件 final_temp_path = pdf_path + '.tmp' diff --git a/sf_base/controllers/controllers.py b/sf_base/controllers/controllers.py index c8447d4e..8694170e 100644 --- a/sf_base/controllers/controllers.py +++ b/sf_base/controllers/controllers.py @@ -4,6 +4,7 @@ import json import logging from odoo import http from odoo.http import request +from odoo.addons.sf_base.decorators.api_log import api_log _logger = logging.getLogger(__name__) @@ -11,6 +12,7 @@ class Manufacturing_Connect(http.Controller): @http.route('/AutoDeviceApi/MachineToolGroup', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, cors="*") + @api_log('机床刀具组', requester='中控系统') def get_maintenance_tool_groups_Info(self, **kw): """ 机床刀具组接口 diff --git a/sf_base/decorators/api_log.py b/sf_base/decorators/api_log.py index 2838ec55..07a7bd00 100644 --- a/sf_base/decorators/api_log.py +++ b/sf_base/decorators/api_log.py @@ -27,6 +27,9 @@ def api_log(name=None, requester=None): # 执行原始函数 result = func(*args, **kwargs) + origin_result = result + if isinstance(result, str): + result = json.loads(result) # 计算响应时间 end_time = datetime.now() @@ -41,7 +44,7 @@ def api_log(name=None, requester=None): 'response_data': json.dumps(result, ensure_ascii=False), 'remote_addr': remote_addr, 'response_time': response_time, - 'status': result.get('code', 500), + 'status': result.get('code') or result.get('ErrorCode') or 500, 'requester': requester, 'responser': '智能工厂' } @@ -49,7 +52,7 @@ def api_log(name=None, requester=None): # 异步创建日志记录 request.env['api.request.log'].sudo().with_context(tracking_disable=True).create(log_vals) - return result + return origin_result except Exception as e: _logger.error(f"API日志记录失败: {str(e)}") diff --git a/sf_base/models/api_log.py b/sf_base/models/api_log.py index 4b630b88..7d54ac80 100644 --- a/sf_base/models/api_log.py +++ b/sf_base/models/api_log.py @@ -1,4 +1,9 @@ from odoo import models, fields, api +import json, ast +import logging +import requests + +_logger = logging.getLogger(__name__) class ApiRequestLog(models.Model): @@ -16,3 +21,52 @@ class ApiRequestLog(models.Model): status = fields.Integer('状态码') requester = fields.Char('请求方') responser = fields.Char('响应方') + + @api.model + def log_request(self, method, url, name=None, responser=None, **kwargs): + # Log the request + request_headers = kwargs.get('headers', {}) + request_body = kwargs.get('json') or kwargs.get('params') or {} + + _logger.info(f"Request: {method} {url} Headers: {request_headers} Body: {request_body}") + + # Make the actual request + response = requests.request(method, url, **kwargs) + + # Log the response + response_status = response.status_code + response_headers = response.headers + response_body = response.text + response_time = response.elapsed.total_seconds() + + _logger.info(f"Response: Status: {response_status} Headers: {response_headers} Body: {response_body}") + + try: + # 如果是字符串,先尝试用 ast.literal_eval 安全地转换成 Python 对象 + if isinstance(response_body, str): + + response_body_obj = json.loads(response_body) + else: + response_body_obj = response_body + + # 再使用 json.dumps 转换成标准的 JSON 字符串 + response_body = json.dumps(response_body_obj, ensure_ascii=False) + except Exception as e: + _logger.warning(f"转换 response_body 到标准 JSON 失败: {str(e)}") + # 如果转换失败,保持原样 + + # Save to database + self.sudo().create({ + 'name': name, + 'path': url, + 'method': method, + 'request_data': request_body, + 'response_data': response_body, + 'remote_addr': None, + 'response_time': response_time, + 'status': response_status, + 'requester': '智能工厂', + 'responser': responser + }) + + return response \ No newline at end of file diff --git a/sf_dlm_management/models/mrp_routing_workcenter.py b/sf_dlm_management/models/mrp_routing_workcenter.py index dd9e59e3..f104cd13 100644 --- a/sf_dlm_management/models/mrp_routing_workcenter.py +++ b/sf_dlm_management/models/mrp_routing_workcenter.py @@ -2,8 +2,8 @@ # from odoo import fields, models, api # from odoo.exceptions import UserError # from odoo.tools import str2bool -# -# + + # class ResMrpRoutingWorkcenter(models.Model): # _inherit = 'mrp.routing.workcenter' # def init(self): diff --git a/sf_dlm_management/models/sf_production_common.py b/sf_dlm_management/models/sf_production_common.py index 42cb9ff2..f3141892 100644 --- a/sf_dlm_management/models/sf_production_common.py +++ b/sf_dlm_management/models/sf_production_common.py @@ -3,12 +3,12 @@ # from odoo import fields, models, api # from odoo.exceptions import UserError # from odoo.tools import str2bool -# -# + + # class SfProductionProcessParameter(models.Model): # _inherit = 'sf.production.process.parameter' -# -# + + # @api.model # def create(self, vals): # # if vals.get('code', '/') == '/' or vals.get('code', '/') is False: @@ -26,7 +26,7 @@ # def create_service_product(self): # service_categ = self.env.ref( # 'sf_dlm.product_category_surface_technics_sf').sudo() -# + # product_name = f"{self.process_id.name}_{self.name}" # product_id = self.env['product.template'].search( # [("name", '=', product_name)]) @@ -48,7 +48,7 @@ # 'partner_id': res_partner.id, # 'price': 1, })], # }) -# + # def create_work_center(self): # production_process_parameter = self # if not production_process_parameter.process_id: @@ -70,7 +70,7 @@ # production_process_parameter.routing_id = routing_id.id # else: # production_process_parameter.routing_id = workcenter_id.id -# + # def init(self): # super(SfProductionProcessParameter, self).init() # # 在模块初始化时触发计算字段的更新 diff --git a/sf_manufacturing/__manifest__.py b/sf_manufacturing/__manifest__.py index 24c8a4b0..f30c1300 100644 --- a/sf_manufacturing/__manifest__.py +++ b/sf_manufacturing/__manifest__.py @@ -48,6 +48,7 @@ 'views/mrp_workorder_batch_replan.xml', 'views/purchase_order_view.xml', 'views/product_template_views.xml', + # 'views/stock_warehouse_orderpoint.xml', ], 'assets': { diff --git a/sf_manufacturing/controllers/controllers.py b/sf_manufacturing/controllers/controllers.py index 64a981ad..70b0b87b 100644 --- a/sf_manufacturing/controllers/controllers.py +++ b/sf_manufacturing/controllers/controllers.py @@ -6,12 +6,14 @@ from datetime import datetime from odoo.addons.sf_manufacturing.models.agv_scheduling import RepeatTaskException from odoo import http from odoo.http import request +from odoo.addons.sf_base.decorators.api_log import api_log class Manufacturing_Connect(http.Controller): @http.route('/AutoDeviceApi/GetWoInfo', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, cors="*") + @api_log('获取工单', requester='中控系统') def get_Work_Info(self, **kw): """ 自动化传递工单号获取工单信息 @@ -54,6 +56,7 @@ class Manufacturing_Connect(http.Controller): @http.route('/AutoDeviceApi/GetShiftPlan', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, cors="*") + @api_log('获取日计划', requester='中控系统') def get_ShiftPlan(self, **kw): """ 自动化每天获取机台日计划 @@ -107,6 +110,7 @@ class Manufacturing_Connect(http.Controller): @http.route('/AutoDeviceApi/QcCheck', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, cors="*") + @api_log('工件预调(前置三元检测)', requester='中控系统') def get_qcCheck(self, **kw): """ 工件预调(前置三元检测) @@ -149,6 +153,7 @@ class Manufacturing_Connect(http.Controller): @http.route('/AutoDeviceApi/FeedBackStart', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") + @api_log('工单开始', requester='中控系统') def button_Work_START(self, **kw): """ 工单任务开始 @@ -198,6 +203,7 @@ class Manufacturing_Connect(http.Controller): @http.route('/AutoDeviceApi/FeedBackEnd', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") + @api_log('工单结束', requester='中控系统') def button_Work_End(self, **kw): """ 工单任务结束 @@ -249,6 +255,7 @@ class Manufacturing_Connect(http.Controller): @http.route('/AutoDeviceApi/PartQualityInspect', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") + @api_log('零件检测(后置三元检测)', requester='中控系统') def PartQualityInspect(self, **kw): """ 零件质检 @@ -295,6 +302,7 @@ class Manufacturing_Connect(http.Controller): @http.route('/AutoDeviceApi/CMMProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") + @api_log('CMM测量程序下载', requester='中控系统') def CMMProgDolod(self, **kw): """ 中控系统传递RFID编号给MES,获取测量程序文件。Ftp下载文件 @@ -335,6 +343,7 @@ class Manufacturing_Connect(http.Controller): @http.route('/AutoDeviceApi/NCProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") + @api_log('CAM加工程序下载', requester='中控系统') def NCProgDolod(self, **kw): """ 中控系统传递RFID编号给MES,获取程序单及程序文件。Ftp下载文件 @@ -376,6 +385,7 @@ class Manufacturing_Connect(http.Controller): @http.route('/AutoDeviceApi/LocationChange', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, cors="*") + @api_log('库位变更', requester='中控系统') def LocationChange(self, **kw): """ 库位变更 @@ -480,6 +490,7 @@ class Manufacturing_Connect(http.Controller): @http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") + @api_log('AGV运送上产线', requester='中控系统') def AGVToProduct(self, **kw): """ AGV运送上产线(完成) @@ -552,6 +563,7 @@ class Manufacturing_Connect(http.Controller): @http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False, cors="*") + @api_log('AGV运送下产线', requester='中控系统') def AGVDownProduct(self, **kw): """ MES调度AGV,搬运零件AGV托盘到产线接驳站。 diff --git a/sf_manufacturing/models/__init__.py b/sf_manufacturing/models/__init__.py index bccaf634..8af783e7 100644 --- a/sf_manufacturing/models/__init__.py +++ b/sf_manufacturing/models/__init__.py @@ -18,3 +18,4 @@ from . import quick_easy_order from . import purchase_order from . import quality_check from . import purchase_request_line +# from . import stock_warehouse_orderpoint \ No newline at end of file diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index 25a016e3..f52cf05d 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -928,12 +928,13 @@ class MrpProduction(models.Model): # 'sf_stock.stock_route_process_outsourcing').id)] # for product_id, request_line_list in grouped_purchase_request_line_sorted_list.items(): # cur_request_line = request_line_list[0] - # cur_request_line['product_qty'] = len(request_line_list) + # # cur_request_line['product_qty'] = cur_request_line['product_qty'] # cur_request_line['request_id'] = pr.id # cur_request_line['origin'] = ", ".join({item['production_name'] for item in request_line_list if item.get('production_name')}) # cur_request_line.pop('group_id', None) # cur_request_line.pop('production_name', None) # self.env["purchase.request.line"].create(cur_request_line) + # pr.button_approved() # 外协出入库单处理 def get_subcontract_pick_purchase(self): diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index 2c32ed9c..cd449c69 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -71,7 +71,7 @@ class ResMrpWorkOrder(models.Model): tracking=True) back_button_display = fields.Boolean(default=False, compute='_compute_back_button_display', store=True) # pr_mp_count = fields.Integer('采购申请单数量', compute='_compute_pr_mp_count', store=True) - # + # @api.depends('state') # def _compute_pr_mp_count(self): # for item in self: @@ -85,6 +85,7 @@ class ResMrpWorkOrder(models.Model): # item.pr_mp_count = len(pr_ids) # else: # item.pr_mp_count = 0 + @api.depends('state') def _compute_back_button_display(self): for record in self: @@ -443,9 +444,8 @@ class ResMrpWorkOrder(models.Model): def _compute_surface_technics_purchase_ids(self): for order in self: if order.routing_type == '表面工艺' and order.state not in ['cancel']: - domain = [('purchase_type', '=', 'consignment'), - ('origin', 'like', '%' + self.production_id.name + '%'), - ('state', '!=', 'cancel')] + domain = [('group_id', '=', self.production_id.procurement_group_id.id), + ('purchase_type', '=', 'consignment'), ('state', '!=', 'cancel')] # domain = [('purchase_type', '=', 'consignment'), # ('origin', 'like', '%' + self.production_id.name + '%'), # ('state', '!=', 'cancel')] @@ -485,6 +485,7 @@ class ResMrpWorkOrder(models.Model): # 'view_mode': 'tree,form', # }) # return action + def action_view_surface_technics_purchase(self): self.ensure_one() # if self.routing_type == '表面工艺': @@ -513,7 +514,8 @@ class ResMrpWorkOrder(models.Model): return result def _get_surface_technics_purchase_ids(self): - domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment'), ('state', '!=', 'cancel')] + domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment'), + ('state', '!=', 'cancel')] # domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment')] # domain = [('group_id', '=', self.production_id.procurement_group_id.id), ('purchase_type', '=', 'consignment')] purchase_orders = self.env['purchase.order'].search(domain, order='id desc') @@ -1243,6 +1245,13 @@ class ResMrpWorkOrder(models.Model): }] return workorders_values_str + # def check_lot_exists(self, picking_id, lot_id): + # return bool( + # picking_id.move_ids.move_line_ids.filtered( + # lambda line: line.lot_id.id == lot_id + # ) + # ) + def _process_compute_state(self): sorted_workorders = sorted(self, key=lambda x: x.sequence) for workorder in sorted_workorders: @@ -1264,10 +1273,17 @@ class ResMrpWorkOrder(models.Model): workorder.state = 'pending' continue # ================= 如果制造订单制造类型为【人工线下加工】========================== + # lot_id = workorder.production_id.move_raw_ids.move_line_ids.lot_id + # picking_ids = workorder.production_id.picking_ids.filtered( + # lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前') + # exists = any( + # move_line.lot_id == lot_id + # for picking in picking_ids + # for move in picking.move_ids + # for move_line in move.move_line_ids + # ) if (workorder.production_id.production_type == '人工线下加工' - and workorder.production_id.schedule_state == '已排' - and len(workorder.production_id.picking_ids.filtered( - lambda w: w.state not in ['done', 'cancel'])) == 0): + and workorder.production_id.schedule_state == '已排'): # and workorder.production_id.programming_state == '已编程' if workorder.is_subcontract is True: if workorder.production_id.state == 'rework': @@ -1276,6 +1292,9 @@ class ResMrpWorkOrder(models.Model): purchase_orders_id = self._get_surface_technics_purchase_ids() if purchase_orders_id.state == 'purchase': workorder.state = 'ready' + # picking_id = workorder.production_id.picking_ids.filtered( + # lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区') + # move_out = picking_id.move_ids move_out = workorder.move_subcontract_workorder_ids[1] for mo in move_out: if mo.state != 'done': @@ -1316,6 +1335,10 @@ class ResMrpWorkOrder(models.Model): if purchase_orders_id.state == 'purchase': workorder.state = 'ready' move_out = workorder.move_subcontract_workorder_ids[1] + # picking_id = workorder.production_id.picking_ids.filtered( + # lambda + # wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区') + # move_out = picking_id.move_ids for mo in move_out: if mo.state != 'done': mo.write({'state': 'assigned', 'production_id': False}) @@ -1325,7 +1348,6 @@ class ResMrpWorkOrder(models.Model): else: workorder.state = 'waiting' - @api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state', 'production_id.tool_state', 'production_id.schedule_state', 'sequence', 'production_id.programming_state') @@ -1357,7 +1379,8 @@ class ResMrpWorkOrder(models.Model): # 判断是否有坯料的序列号信息 boolean = False if self.production_id.move_raw_ids: - if self.production_id.move_raw_ids[0].product_id.categ_type == '坯料' and self.production_id.move_raw_ids[0].product_id.tracking == 'serial': + if self.production_id.move_raw_ids[0].product_id.categ_type == '坯料' and \ + self.production_id.move_raw_ids[0].product_id.tracking == 'serial': if self.production_id.move_raw_ids[0].move_line_ids: if self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name: boolean = True @@ -1390,6 +1413,10 @@ class ResMrpWorkOrder(models.Model): if self.routing_type == '表面工艺': if self.is_subcontract is True: move_out = self.move_subcontract_workorder_ids[1] + # picking_id = self.production_id.picking_ids.filtered( + # lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区') + # move_out = picking_id.move_ids + # move_out = self.move_subcontract_workorder_ids[1] # move_out = self.env['stock.move'].search( # [('location_id', '=', self.env['stock.location'].search( # [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id), @@ -1808,7 +1835,7 @@ class ResMrpWorkOrder(models.Model): orderby=orderby, lazy=lazy ) - + model_id = fields.Char('模型ID', related='production_id.model_id') diff --git a/sf_manufacturing/models/purchase_order.py b/sf_manufacturing/models/purchase_order.py index 111c09e3..e967215d 100644 --- a/sf_manufacturing/models/purchase_order.py +++ b/sf_manufacturing/models/purchase_order.py @@ -59,6 +59,86 @@ class PurchaseOrder(models.Model): production_id = self.env['mrp.production'].search([('origin', 'in', origins)]) purchase.production_count = len(production_id) + # def process_replenish(self,production,total_qty): + # record = self + # bom_line_id = production.bom_id.bom_line_ids + # replenish = self.env['stock.warehouse.orderpoint'].search([ + # ('product_id', '=', bom_line_id.product_id.id), + # ( + # 'location_id', '=', self.env.ref('sf_stock.stock_location_outsourcing_material_receiving_area').id), + # # ('state', 'in', ['draft', 'confirmed']) + # ], limit=1) + # if not replenish: + # replenish_model = self.env['stock.warehouse.orderpoint'] + # replenish = replenish_model.create({ + # 'product_id': bom_line_id.product_id.id, + # 'location_id': self.env.ref( + # 'sf_stock.stock_location_outsourcing_material_receiving_area').id, + # 'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id, + # 'group_id': record.group_id.id, + # 'qty_to_order': total_qty, + # 'origin': record.name, + # }) + # else: + # replenish.write({ + # 'product_id': bom_line_id.product_id.id, + # 'location_id': self.env.ref( + # 'sf_stock.stock_location_outsourcing_material_receiving_area').id, + # 'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id, + # 'group_id': record.group_id.id, + # 'qty_to_order': total_qty + replenish.qty_to_order, + # 'origin': record.name + ',' + replenish.origin, + # }) + # replenish.action_replenish() + + # def outsourcing_service_replenishment(self): + # record = self + # if record.purchase_type != 'consignment': + # return + # grouped_lines = {} + # for line in record.order_line: + # if line.related_product.id not in grouped_lines: + # grouped_lines[line.related_product.id] = [] + # grouped_lines[line.related_product.id].append(line) + # for product_id,lines in grouped_lines.items(): + # production = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1) + # if not production: + # continue + # total_qty = sum(line.product_qty for line in lines) + # record.process_replenish(production,total_qty) + # for product_id,lines in grouped_lines.items(): + # productions = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1) + # if not productions: + # continue + # # production.bom_id.bom_line_ids.product_id + # location_id = self.env['stock.location'].search([('name', '=', '制造前')]) + # quants = self.env['stock.quant'].search([ + # ('product_id', '=', productions.bom_id.bom_line_ids.product_id.id), + # ('location_id', '=', location_id.id) + # ]) + # total_qty = sum(quants.mapped('quantity')) # 计算该位置的总库存量 + # is_available = total_qty > 0 + # if not is_available: + # raise UserError('请先完成坯料入库') + # for production_id in productions: + # work_ids = production_id.workorder_ids.filtered( + # lambda wk: wk.state not in ['done', 'rework', 'cancel']) + # if not work_ids: + # continue + # min_sequence_wk = min(work_ids, key=lambda wk: wk.sequence) + # if min_sequence_wk.is_subcontract: + # picking_id = production_id.picking_ids.filtered( + # lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区') + # move_out = picking_id.move_ids + # for mo in move_out: + # if mo.state != 'done': + # mo.write({'state': 'assigned', 'production_id': False}) + # if not mo.move_line_ids: + # self.env['stock.move.line'].create( + # mo.get_move_line(production_id, min_sequence_wk)) + # product = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1) + # match = re.search(r'(S\d{5}-\d)',product.name) + # pass def button_confirm(self): for record in self: for line in record.order_line: @@ -66,37 +146,10 @@ class PurchaseOrder(models.Model): raise UserError('请对【产品】中的【数量】进行输入') if line.price_unit <= 0: raise UserError('请对【产品】中的【单价】进行输入') - # if record.purchase_type == 'consignment': - # bom_line_id = record.order_line[0].purchase_request_lines.request_id.bom_id.bom_line_ids - # replenish = self.env['stock.warehouse.orderpoint'].search([ - # ('product_id', '=', bom_line_id.product_id.id), - # ( - # 'location_id', '=', self.env.ref('sf_stock.stock_location_outsourcing_material_receiving_area').id), - # # ('state', 'in', ['draft', 'confirmed']) - # ], limit=1) - # if not replenish: - # replenish_model = self.env['stock.warehouse.orderpoint'] - # replenish = replenish_model.create({ - # 'product_id': bom_line_id.product_id.id, - # 'location_id': self.env.ref( - # 'sf_stock.stock_location_outsourcing_material_receiving_area').id, - # 'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id, - # 'group_id': record.group_id.id, - # 'qty_to_order': 1, - # 'origin': record.name, - # }) - # else: - # replenish.write({ - # 'product_id': bom_line_id.product_id.id, - # 'location_id': self.env.ref( - # 'sf_stock.stock_location_outsourcing_material_receiving_area').id, - # 'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id, - # 'group_id': record.group_id.id, - # 'qty_to_order': 1 + replenish.qty_to_order, - # 'origin': record.name + ',' + replenish.origin, - # }) - # replenish.action_replenish() + # record.outsourcing_service_replenishment() + res = super(PurchaseOrder, self).button_confirm() + for line in self.order_line: # 将产品不追踪序列号的行项目设置qty_done if line.move_ids and line.move_ids[0].product_id.tracking == 'none': diff --git a/sf_manufacturing/models/purchase_request_line.py b/sf_manufacturing/models/purchase_request_line.py index eca52d3b..487683a0 100644 --- a/sf_manufacturing/models/purchase_request_line.py +++ b/sf_manufacturing/models/purchase_request_line.py @@ -1,4 +1,4 @@ -# # -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- # import base64 # import datetime # import logging @@ -7,24 +7,24 @@ # import re # import traceback # from operator import itemgetter -# + # import requests # from itertools import groupby # from collections import defaultdict, namedtuple -# + # from odoo import api, fields, models, SUPERUSER_ID, _ # from odoo.exceptions import UserError, ValidationError # from odoo.tools import float_compare, float_round, float_is_zero, format_datetime -# -# + + # class PurchaseRequestLine(models.Model): # _inherit = 'purchase.request' # is_subcontract = fields.Boolean(string='是否外协',default=False) # class PurchaseRequestLine(models.Model): # _inherit = 'purchase.request.line' # is_subcontract = fields.Boolean(string='是否外协') -# -# + + # class PurchaseRequest(models.Model): # _inherit = 'purchase.request' # bom_id = fields.Many2one('mrp.bom') diff --git a/sf_manufacturing/models/sale_order.py b/sf_manufacturing/models/sale_order.py index 4ce8750c..8251a78d 100644 --- a/sf_manufacturing/models/sale_order.py +++ b/sf_manufacturing/models/sale_order.py @@ -56,10 +56,10 @@ class SaleOrder(models.Model): 'jikimo_sale_multiple_supply_methods.product_template_manual_processing').sudo() # 复制成品模板上的属性 - line.product_id.product_tmpl_id.copy_template(product_template_id) + line.product_id.copy_template(product_template_id) # 将模板上的single_manufacturing属性复制到成品上 - line.product_id.single_manufacturing = product_template_id.single_manufacturing - line.product_id.tracking = product_template_id.tracking + # line.product_id.single_manufacturing = product_template_id.single_manufacturing + # line.product_id.tracking = product_template_id.tracking order_id = self product = line.product_id @@ -76,7 +76,7 @@ class SaleOrder(models.Model): 'embryo_redundancy_id': line.embryo_redundancy_id, } product_name = '' - match = re.search(r'(S\d{5}-\d)', product.name) + match = re.search(r'(S\d{5}-\d*)', product.name) # 如果匹配成功,提取结果 if match: product_name = match.group(0) diff --git a/sf_manufacturing/models/sf_production_common.py b/sf_manufacturing/models/sf_production_common.py index 2cdfd750..203dd221 100644 --- a/sf_manufacturing/models/sf_production_common.py +++ b/sf_manufacturing/models/sf_production_common.py @@ -20,13 +20,13 @@ class SfProductionProcessParameter(models.Model): # is_product_button = fields.Boolean(compute='_compute_is_product_button',default=False) # is_delete_button = fields.Boolean(compute='_compute_is_delete_button', default=False) # routing_id = fields.Many2one('mrp.routing.workcenter', string="工序") - # + # @api.depends('outsourced_service_products') # def _compute_service_products(self): # for record in self: # # 假设取第一条作为主明细 - # record.service_products = record.outsourced_service_products.id if record.outsourced_service_products else False - # + # record.service_products = record.outsourced_service_products.ids if record.outsourced_service_products else False + # def _inverse_service_products(self): # for record in self: # if record.service_products: @@ -45,7 +45,7 @@ class SfProductionProcessParameter(models.Model): # for record in self: # if len(record.outsourced_service_products) > 1: # raise ValidationError("工艺参数不能与多个产品关联") - # + # @api.onchange('outsourced_service_products') # def _onchange_validate_partner_limit(self): # for record in self: @@ -58,7 +58,7 @@ class SfProductionProcessParameter(models.Model): # record.is_product_button = True # else: # record.is_product_button = False - # + # def has_wksp_prefix(self): # """ # 判断字符串是否以WKSP开头(不区分大小写) diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index 8572b620..d8caae88 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -631,6 +631,62 @@ class StockPicking(models.Model): move.action_clear_lines_show_details() move.action_show_details() res = super().button_validate() + # lot_ids = None + # product_ids = self.move_ids.mapped('product_id') + # if not self.move_ids[0].product_id.single_manufacturing and self.move_ids[0].product_id.tracking == 'none': + # lot_ids = self.move_ids.move_line_ids.mapped('lot_id') + # production_ids = self.sale_order_id.mrp_production_ids if self.sale_order_id else self.env['mrp.production'] + # if res and self.location_id.name == '外协收料区' and self.location_dest_id.name == '制造前': + # # 如果是最后一张外协入库单,则设置库存位置的预留数量 + # for production_id in production_ids: + # if lot_ids: + # lot_id = production_id.move_raw_ids.move_line_ids.lot_id + # # picking_ids = production_id.picking_ids.filtered( + # # lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前') + # if lot_id in lot_ids: + # workorder_id = production_id.workorder_ids.filtered( + # lambda a: a.state == 'progress' and a.is_subcontract) + # if not workorder_id: + # continue + # workorder_id.button_finish() + # else: + # workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'progress' and a.is_subcontract) + # if not workorder_id: + # continue + # workorder_id.button_finish() + # # lot_id = workorder.production_id.move_raw_ids.move_line_ids.lot_id + # # picking_ids = workorder.production_id.picking_ids.filtered( + # # lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前') + + # # if move_in: + # # workorder = move_in.subcontract_workorder_id + # # workorders = workorder.production_id.workorder_ids + # # subcontract_workorders = workorders.filtered( + # # lambda wo: wo.is_subcontract == True and wo.state != 'cancel').sorted('sequence') + # # # if workorder == subcontract_workorders[-1]: + # # # self.env['stock.quant']._update_reserved_quantity( + # # # move_in.product_id, move_in.location_dest_id, move_in.product_uom_qty, + # # # lot_id=move_in.move_line_ids.lot_id, + # # # package_id=False, owner_id=False, strict=False + # # # ) + # # workorder.button_finish() + # if res and self.location_id.name == '制造前' and self.location_dest_id.name == '外协加工区': + # for production_id in production_ids: + # if lot_ids: + # lot_id = production_id.move_raw_ids.move_line_ids.lot_id + # # picking_ids = production_id.picking_ids.filtered( + # # lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前') + # if lot_id in lot_ids: + # workorder_id = production_id.workorder_ids.filtered( + # lambda a: a.state == 'progress' and a.is_subcontract) + # if not workorder_id: + # continue + # workorder_id.button_finish() + # else: + # workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'ready' and a.is_subcontract) + # if not workorder_id: + # continue + # workorder_id.button_start() picking_type_in = self.env.ref('sf_manufacturing.outcontract_picking_in').id if res is True and self.picking_type_id.id == picking_type_in: # 如果是最后一张外协入库单,则设置库存位置的预留数量 diff --git a/sf_manufacturing/models/stock_warehouse_orderpoint.py b/sf_manufacturing/models/stock_warehouse_orderpoint.py new file mode 100644 index 00000000..09289f52 --- /dev/null +++ b/sf_manufacturing/models/stock_warehouse_orderpoint.py @@ -0,0 +1,11 @@ +# -*- coding: utf-8 -*- +# Part of SmartGo. See LICENSE file for full copyright and licensing details. +import base64 +from io import BytesIO +from odoo import api, fields, models, SUPERUSER_ID, _ + + +class StockWarehouseOrderpoint(models.Model): + _inherit = 'stock.warehouse.orderpoint' + origin = fields.Char(string='来源') + _order = 'create_date DESC' \ No newline at end of file diff --git a/sf_manufacturing/views/mrp_routing_workcenter_view.xml b/sf_manufacturing/views/mrp_routing_workcenter_view.xml index d23e1616..98968058 100644 --- a/sf_manufacturing/views/mrp_routing_workcenter_view.xml +++ b/sf_manufacturing/views/mrp_routing_workcenter_view.xml @@ -22,26 +22,26 @@ - - - - - - - - - - - - - - - - - - - - + + + diff --git a/sf_manufacturing/views/mrp_workorder_view.xml b/sf_manufacturing/views/mrp_workorder_view.xml index c519ff70..f388ace0 100644 --- a/sf_manufacturing/views/mrp_workorder_view.xml +++ b/sf_manufacturing/views/mrp_workorder_view.xml @@ -144,17 +144,17 @@ statusbar_visible="pending,waiting,ready,progress,to be detected,done,rework"/> - - - - - - - - - - - +