diff --git a/jikimo_frontend/static/src/js/custom_form_status_indicator.js b/jikimo_frontend/static/src/js/custom_form_status_indicator.js index 46e3ad70..4992872a 100644 --- a/jikimo_frontend/static/src/js/custom_form_status_indicator.js +++ b/jikimo_frontend/static/src/js/custom_form_status_indicator.js @@ -139,6 +139,7 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', { owl.onMounted(() => { this.activeElement = this.uiService.activeElement; this.setRequired() + this.listherHeaderBodyNum() }) return this._super(...arguments); }, @@ -165,6 +166,26 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', { } catch (e) { console.log(e) } + }, + listherHeaderBodyNum() { + const dom = this.tableRef.el + try { + const thead = $(dom).children('thead') + const tbody = $(dom).children('tbody') + const thead_tr = thead.children().eq(0) + const tbody_tr = tbody.children().eq(0) + const thead_th_num = thead_tr.children().length + const tbody_tr_num = tbody_tr.children().length + const num = thead_th_num - tbody_tr_num + if(num == -1) { + tbody.children('tr').each(function () { + $(this).children('td').eq(0).remove() + }) + } + + } catch (e) { + console.log(e) + } } }) diff --git a/jikimo_purchase_tier_validation/__init__.py b/jikimo_purchase_tier_validation/__init__.py new file mode 100644 index 00000000..40c32597 --- /dev/null +++ b/jikimo_purchase_tier_validation/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- + +from . import controllers +from . import models +from . import wizards diff --git a/jikimo_purchase_tier_validation/__manifest__.py b/jikimo_purchase_tier_validation/__manifest__.py new file mode 100644 index 00000000..a7bdbddf --- /dev/null +++ b/jikimo_purchase_tier_validation/__manifest__.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- +{ + 'name': "机企猫 采购审批流程", + + 'summary': """ + Short (1 phrase/line) summary of the module's purpose, used as + subtitle on modules listing or apps.openerp.com""", + + 'description': """ + Long description of module's purpose + """, + + 'author': "My Company", + 'website': "https://www.yourcompany.com", + + # Categories can be used to filter modules in modules listing + # Check https://github.com/odoo/odoo/blob/16.0/odoo/addons/base/data/ir_module_category_data.xml + # for the full list + 'category': 'Uncategorized', + 'version': '0.1', + + # any module necessary for this one to work correctly + 'depends': ['purchase', 'purchase_tier_validation', 'documents', 'purchase_request', 'account', 'purchase_order_approved'], + + # always loaded + 'data': [ + 'security/ir.model.access.csv', + 'data/documents_data.xml', + 'wizards/upload_file_wizard_view.xml', + 'views/views.xml', + ], + # only loaded in demonstration mode + 'demo': [ + 'demo/demo.xml', + ], + + 'assets': { + 'web.assets_backend': [ + 'jikimo_purchase_tier_validation/static/src/js/ir_model_extend.js', + ], + }, +} diff --git a/jikimo_purchase_tier_validation/controllers/__init__.py b/jikimo_purchase_tier_validation/controllers/__init__.py new file mode 100644 index 00000000..457bae27 --- /dev/null +++ b/jikimo_purchase_tier_validation/controllers/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import controllers \ No newline at end of file diff --git a/jikimo_purchase_tier_validation/controllers/controllers.py b/jikimo_purchase_tier_validation/controllers/controllers.py new file mode 100644 index 00000000..6bbe5e40 --- /dev/null +++ b/jikimo_purchase_tier_validation/controllers/controllers.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# from odoo import http + + +# class JikimoPurchaseTierValidation(http.Controller): +# @http.route('/jikimo_purchase_tier_validation/jikimo_purchase_tier_validation', auth='public') +# def index(self, **kw): +# return "Hello, world" + +# @http.route('/jikimo_purchase_tier_validation/jikimo_purchase_tier_validation/objects', auth='public') +# def list(self, **kw): +# return http.request.render('jikimo_purchase_tier_validation.listing', { +# 'root': '/jikimo_purchase_tier_validation/jikimo_purchase_tier_validation', +# 'objects': http.request.env['jikimo_purchase_tier_validation.jikimo_purchase_tier_validation'].search([]), +# }) + +# @http.route('/jikimo_purchase_tier_validation/jikimo_purchase_tier_validation/objects/', auth='public') +# def object(self, obj, **kw): +# return http.request.render('jikimo_purchase_tier_validation.object', { +# 'object': obj +# }) diff --git a/jikimo_purchase_tier_validation/data/documents_data.xml b/jikimo_purchase_tier_validation/data/documents_data.xml new file mode 100644 index 00000000..990a6418 --- /dev/null +++ b/jikimo_purchase_tier_validation/data/documents_data.xml @@ -0,0 +1,11 @@ + + + + + + 采购合同 + 存放采购合同相关文件 + 10 + + + \ No newline at end of file diff --git a/jikimo_purchase_tier_validation/demo/demo.xml b/jikimo_purchase_tier_validation/demo/demo.xml new file mode 100644 index 00000000..cbba42ce --- /dev/null +++ b/jikimo_purchase_tier_validation/demo/demo.xml @@ -0,0 +1,30 @@ + + + + + \ No newline at end of file diff --git a/jikimo_purchase_tier_validation/models/__init__.py b/jikimo_purchase_tier_validation/models/__init__.py new file mode 100644 index 00000000..5305644d --- /dev/null +++ b/jikimo_purchase_tier_validation/models/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models \ No newline at end of file diff --git a/jikimo_purchase_tier_validation/models/models.py b/jikimo_purchase_tier_validation/models/models.py new file mode 100644 index 00000000..d90ed257 --- /dev/null +++ b/jikimo_purchase_tier_validation/models/models.py @@ -0,0 +1,215 @@ +from odoo import models, fields, api, _ +from odoo.exceptions import ValidationError +import logging + +_logger = logging.getLogger(__name__) + + +class jikimo_purchase_tier_validation(models.Model): + _name = 'purchase.order' + _inherit = ['purchase.order', 'tier.validation'] + _description = "采购订单" + + _tier_validation_buttons_xpath = "/form/header/button[@id='draft_confirm'][1]" + + contract_document_id = fields.Many2one('documents.document', string='合同文件') + contract_file = fields.Binary(related='contract_document_id.datas', string='合同文件内容') + contract_file_name = fields.Char(related='contract_document_id.attachment_id.name', string='文件名') + + # 是否已上传合同文件 + is_upload_contract_file = fields.Boolean(string='是否已上传合同文件', default=False) + + def button_confirm(self): + for record in self: + if record.need_validation and record.validation_status != 'validated': + raise ValidationError(_('此操作需要至少对一条记录进行审批。\n请发起审批申请。')) + if record.state in ['to approve']: + raise ValidationError(_('请先完成审批。')) + if record.state == 'approved': + record.state = 'purchase' + return super().button_confirm() + + # def button_confirm(self): + # self = self.with_context(skip_validation=True) + # return super().button_confirm() + # + # def _check_state_conditions(self, vals): + # self.ensure_one() + # if self._context.get('skip_validation'): + # return False + # return ( + # self._check_state_from_condition() + # and vals.get(self._state_field) in self._state_to + # ) + + def request_validation(self): + for record in self: + error_messages = [] + + # 检查必填字段 + required_fields = { + 'partner_ref': '合同名称', + 'contract_number': '合同编号' + } + + missing_fields = [ + name for field, name in required_fields.items() + if not record[field] + ] + + if missing_fields: + error_messages.append('* 如下字段要求必须填写:%s' % '、'.join(missing_fields)) + + # 检查合同文件 + if not record.contract_document_id: + error_messages.append('* 必须点击上传合同文件') + + # 如果有任何错误,一次性显示所有错误信息 + if error_messages: + raise ValidationError('\n'.join(error_messages)) + + # 添加通知消息 + if hasattr(record, 'message_post'): + current_user = self.env.user.name + record.message_post( + body=f"{current_user} 提交审批", + message_type='notification', + subtype_xmlid='mail.mt_note' + ) + res = super(jikimo_purchase_tier_validation, self).request_validation() + self.state = 'to approve' + return res + + def restart_validation(self): + res = super(jikimo_purchase_tier_validation, self).restart_validation() + self.state = 'draft' + return res + + def _validate_tier(self, tiers=False): + res = super(jikimo_purchase_tier_validation, self)._validate_tier(tiers) + tier_reviews = tiers or self.review_ids + + # 检查是否所有审批都已通过 + all_approved = all( + tier_review.status == 'approved' + for tier_review in tier_reviews + ) + + if all_approved and tier_reviews: # 确保有审批记录 + self.state = 'approved' + + return res + + def _rejected_tier(self, tiers=False): + res = super(jikimo_purchase_tier_validation, self)._rejected_tier(tiers) + self.state = 'draft' + return res + + @api.model + def _get_under_validation_exceptions(self): + res = super(jikimo_purchase_tier_validation, self)._get_under_validation_exceptions() + res.append("state") + return res + + # 上传合同文件 + def upload_contract_file(self): + print('upload_contract_file===========================') + # self.ensure_one() + # return { + # 'name': _('上传合同文件'), + # 'type': 'ir.actions.act_window', + # 'res_model': 'ir.attachment', + # 'view_mode': 'form', + # 'view_type': 'form', + # 'target': 'new', + # 'context': { + # 'default_res_model': self._name, + # 'default_res_id': self.id, + # 'default_type': 'binary', + # 'default_mimetype': 'application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,image/jpeg,image/png', + # } + # } + + self.ensure_one() + action = { + 'type': 'ir.actions.act_window', + 'name': _('上传合同文件'), + 'res_model': 'ir.attachment.wizard', # 我们需要创建一个新的向导模型 + 'view_mode': 'form', + 'target': 'new', + 'context': { + 'default_res_model': self._name, + 'default_res_id': self.id, + } + } + return action + + # 删除合同文件 + def delete_contract_file(self): + self.ensure_one() + if self.contract_document_id: + try: + document = self.contract_document_id + + # 清空关联 + self.write({ + 'contract_document_id': False, + 'contract_file': False, + 'contract_file_name': False + }) + + # 删除文档 + if document: + document.with_context(no_attachment=True).sudo().unlink() + + self.is_upload_contract_file = False + + # 返回视图动作来刷新当前表单 + return { + 'type': 'ir.actions.act_window', + 'res_model': 'purchase.order', + 'res_id': self.id, + 'view_mode': 'form', + 'view_type': 'form', + 'target': 'current', + 'flags': {'mode': 'readonly'}, + } + + except Exception as e: + _logger.error('删除合同文件时出错: %s', str(e)) + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': _('错误'), + 'message': _('删除文件时出现错误'), + 'type': 'danger', + 'sticky': True, + } + } + + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': _('提示'), + 'message': _('没有需要删除的合同文件'), + 'type': 'warning', + 'sticky': False, + } + } + + +class jikimo_purchase_request(models.Model): + _inherit = 'purchase.request' + _description = "采购申请" + + +class jikimo_account_payment(models.Model): + _inherit = 'account.payment' + _description = "付款单" + + +class jikimo_account_move(models.Model): + _inherit = 'account.move' + _description = "发票账单" diff --git a/jikimo_purchase_tier_validation/security/ir.model.access.csv b/jikimo_purchase_tier_validation/security/ir.model.access.csv new file mode 100644 index 00000000..09ba96bc --- /dev/null +++ b/jikimo_purchase_tier_validation/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_ir_attachment_wizard,ir.attachment.wizard,model_ir_attachment_wizard,base.group_user,1,1,1,1 \ No newline at end of file diff --git a/jikimo_purchase_tier_validation/static/src/js/ir_model_extend.js b/jikimo_purchase_tier_validation/static/src/js/ir_model_extend.js new file mode 100644 index 00000000..97d6f38c --- /dev/null +++ b/jikimo_purchase_tier_validation/static/src/js/ir_model_extend.js @@ -0,0 +1,14 @@ +/** @odoo-module **/ + +import {registerPatch} from "@mail/model/model_core"; + +registerPatch({ + name: "ir.model.review", + fields: { + availableWebViews: { + compute() { + return ["list", "form", "activity"]; + }, + }, + }, +}); \ No newline at end of file diff --git a/jikimo_purchase_tier_validation/views/templates.xml b/jikimo_purchase_tier_validation/views/templates.xml new file mode 100644 index 00000000..cea6b39a --- /dev/null +++ b/jikimo_purchase_tier_validation/views/templates.xml @@ -0,0 +1,24 @@ + + + + + \ No newline at end of file diff --git a/jikimo_purchase_tier_validation/views/views.xml b/jikimo_purchase_tier_validation/views/views.xml new file mode 100644 index 00000000..b651e914 --- /dev/null +++ b/jikimo_purchase_tier_validation/views/views.xml @@ -0,0 +1,98 @@ + + + + + tier_validation_view_approved_purchase_order_form_inherit + purchase.order + + + + + + + + + tier_validation_view_purchase_order_form_inherit + purchase.order + + + + + + + + + + + + + diff --git a/quality_control/__manifest__.py b/quality_control/__manifest__.py index d8dc1ed2..59fbbb98 100644 --- a/quality_control/__manifest__.py +++ b/quality_control/__manifest__.py @@ -8,7 +8,7 @@ 'sequence': 120, 'summary': 'Control the quality of your products', 'website': 'https://www.odoo.com/app/quality', - 'depends': ['quality'], + 'depends': ['quality', 'sf_manufacturing'], 'description': """ Quality Control =============== diff --git a/quality_control/models/quality.py b/quality_control/models/quality.py index ebe73384..011c7914 100644 --- a/quality_control/models/quality.py +++ b/quality_control/models/quality.py @@ -7,6 +7,7 @@ from datetime import datetime import random from odoo import api, models, fields, _ +from odoo.api import depends from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_round from odoo.osv.expression import OR @@ -122,7 +123,13 @@ class QualityPoint(models.Model): class QualityCheck(models.Model): _inherit = "quality.check" - + part_name = fields.Char('零件名称', compute='_compute_part_name_number', readonly=True) + part_number = fields.Char('零件图号', compute='_compute_part_name_number', readonly=True) + @depends('product_id') + def _compute_part_name_number(self): + for record in self: + record.part_number = record.product_id.part_number + record.part_name = record.product_id.part_name failure_message = fields.Html(related='point_id.failure_message', readonly=True) measure = fields.Float('Measure', default=0.0, digits='Quality Tests', tracking=True) measure_success = fields.Selection([ @@ -152,6 +159,34 @@ class QualityCheck(models.Model): is_lot_tested_fractionally = fields.Boolean(related='point_id.is_lot_tested_fractionally') testing_percentage_within_lot = fields.Float(related="point_id.testing_percentage_within_lot") product_tracking = fields.Selection(related='product_id.tracking') + quality_check_type = fields.Selection([ + ('采购入库检', '采购入库检'), + ('客供料入库检', '客供料入库检'), + ('退货入库检', '退货入库检'), + ('生产入库检', '生产入库检'), + ('外协入库检', '外协入库检'), + ('成品发货检', '成品发货检'), + ('工序外协发货检', '工序外协发货检'), + ('委外坯料发货检', '委外坯料发货检')], string='类型', compute='_compute_quality_check_type', store=True) + + @api.depends('picking_id') + def _compute_quality_check_type(self): + for check in self: + if check.picking_id: + picking_type = check.picking_id.picking_type_id.sequence_code + type_mapping = { + 'IN': '采购入库检', + 'DL': '客供料入库检', + 'RET': '退货入库检', + 'SFP': '生产入库检', + 'OCIN': '外协入库检', + 'OUT': '成品发货检', + 'OCOUT': '工序外协发货检', + 'RES': '委外坯料发货检', + } + check.quality_check_type = type_mapping.get(picking_type, False) + else: + check.quality_check_type = False @api.depends('measure_success') def _compute_warning_message(self): @@ -294,6 +329,19 @@ class QualityAlert(models.Model): _inherit = "quality.alert" title = fields.Char('Title') + part_number = fields.Char(string='零件图号', compute='_compute_part_info', store=True) + part_name = fields.Char(string='零件名称', compute='_compute_part_info', store=True) + + @api.depends('product_id', 'picking_id') + def _compute_part_info(self): + for alert in self: + if alert.product_tmpl_id.categ_id.name == '成品': + alert.part_number = alert.product_id.part_number + alert.part_name = alert.product_id.part_name + elif alert.product_id.categ_id.name == '坯料': + if alert.picking_id.move_ids_without_package: + alert.part_number = alert.picking_id.move_ids_without_package[0].part_number + alert.part_name = alert.picking_id.move_ids_without_package[0].part_name def action_see_check(self): return { diff --git a/quality_control/views/quality_views.xml b/quality_control/views/quality_views.xml index 8e5af75a..c40d42b2 100644 --- a/quality_control/views/quality_views.xml +++ b/quality_control/views/quality_views.xml @@ -90,6 +90,8 @@ + + @@ -150,6 +152,10 @@ + + + + @@ -260,6 +266,8 @@ + + @@ -389,6 +397,8 @@ + + @@ -446,6 +456,10 @@ + + + + @@ -505,6 +519,7 @@ Quality Checks quality.check tree,kanban,form,pivot,graph + {'is_web_request': True}

No quality check found diff --git a/sf_bf_connect/models/jd_eclp.py b/sf_bf_connect/models/jd_eclp.py index 58691ca0..aa27736c 100644 --- a/sf_bf_connect/models/jd_eclp.py +++ b/sf_bf_connect/models/jd_eclp.py @@ -151,6 +151,12 @@ class JdEclp(models.Model): _logger.info('准备调接口1') url1 = config['bfm_url_new'] + '/api/create/jd/order' requests.post(url1, json=json1, data=None) + # ===============修改销售订单状态为【物流中】=================== + item = self.env['sale.order'].sudo().search([('name', '=', self.origin)]) + if not item: + raise ValidationError('没有查询到订单号为【%s】的销售订单!' % self.origin) + else: + item.write({'state': 'physical_distribution'}) _logger.info('调用成功1') _logger.info('准备调接口2') json2 = { diff --git a/sf_bf_connect/models/process_status.py b/sf_bf_connect/models/process_status.py index 09fecfae..69eb84f1 100644 --- a/sf_bf_connect/models/process_status.py +++ b/sf_bf_connect/models/process_status.py @@ -36,6 +36,8 @@ class StatusChange(models.Model): # 使用super()来调用原始方法(在本例中为'sale.order'模型的'action_confirm'方法) try: res = super(StatusChange, self).action_confirm() + # 修改销售订单状态为【加工中】 + self.write({'state': 'processing'}) logging.info('原生方法返回结果:%s' % res) # 原有方法执行后,进行额外的操作(如调用外部API) process_start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') diff --git a/sf_dlm/models/stock_rule_inherit.py b/sf_dlm/models/stock_rule_inherit.py index 4a40ca2f..4d715196 100644 --- a/sf_dlm/models/stock_rule_inherit.py +++ b/sf_dlm/models/stock_rule_inherit.py @@ -9,9 +9,9 @@ class StockRuleInherit(models.Model): @api.model def _run_buy(self, procurements): # 判断补货组的采购类型 - procurements_group = {'standard': [], 'consignment': []} + procurements_group = {'standard': [], 'outsourcing': []} for procurement, rule in procurements: - is_consignment = False + is_outsourcing = False product = procurement.product_id # 获取主 BOM bom = self.env['mrp.bom'].search([('product_tmpl_id', '=', product.product_tmpl_id.id)], limit=1) @@ -24,17 +24,17 @@ class StockRuleInherit(models.Model): for route in raw_material.route_ids: # print('route.name:', route.name) if route.name == '按订单补给外包商': - is_consignment = True + is_outsourcing = True - if is_consignment: - procurements_group['consignment'].append((procurement, rule)) + if is_outsourcing: + procurements_group['outsourcing'].append((procurement, rule)) else: procurements_group['standard'].append((procurement, rule)) for key, value in procurements_group.items(): super(StockRuleInherit, self)._run_buy(value) - if key == 'consignment': + if key == 'outsourcing': for procurement, rule in value: supplier = procurement.values.get('supplier') if supplier: @@ -49,7 +49,7 @@ class StockRuleInherit(models.Model): ], limit=1) logging.info("po=: %s", po) if po: - po.write({'purchase_type': 'consignment'}) + po.write({'purchase_type': 'outsourcing'}) # # 首先调用父类的 _run_buy 方法,以保留原有逻辑 # super(StockRuleInherit, self)._run_buy(procurements) @@ -83,5 +83,5 @@ class StockRuleInherit(models.Model): # ], limit=1) # logging.info("po=: %s", po) # if po: - # po.write({'purchase_type': 'consignment'}) + # po.write({'purchase_type': 'outsourcing'}) # break diff --git a/sf_hr/models/hr_employee.py b/sf_hr/models/hr_employee.py index 5d37f199..2a93ddc5 100644 --- a/sf_hr/models/hr_employee.py +++ b/sf_hr/models/hr_employee.py @@ -23,6 +23,16 @@ class JkmPracticeEmployee(models.Model): vals["we_id"] = self._get_we_id(vals.get('work_email')) return super(JkmPracticeEmployee, self).write(vals) + def unlink(self): + for record in self: + res_partner_obj = record.env['res.partner'].sudo().search([('email', '=', record.work_email)]) + if res_partner_obj: + res_partner_obj.unlink() + res = super(JkmPracticeEmployee, self).unlink() + + return res + + @api.depends('work_contact_id', 'work_contact_id.mobile', 'work_contact_id.email') def _compute_work_contact_details(self): for employee in self: diff --git a/sf_machine_connect/__manifest__.py b/sf_machine_connect/__manifest__.py index 48857914..50a157d4 100644 --- a/sf_machine_connect/__manifest__.py +++ b/sf_machine_connect/__manifest__.py @@ -25,7 +25,7 @@ # 只有它被屏蔽了 # 'views/SfWorkOrderBarcodes.xml', 'views/WorkCenterBarcodes.xml', - 'views/Stock_picking_Barcodes.xml', + # 'views/Stock_picking_Barcodes.xml', 'views/machine_monitor.xml', 'views/machine_info_present.xml', 'views/delivery_record.xml', diff --git a/sf_manufacturing/__manifest__.py b/sf_manufacturing/__manifest__.py index 991b26ab..936afaaf 100644 --- a/sf_manufacturing/__manifest__.py +++ b/sf_manufacturing/__manifest__.py @@ -24,6 +24,7 @@ 'wizard/production_wizard_views.xml', 'wizard/production_technology_wizard_views.xml', 'wizard/production_technology_re_adjust_wizard_views.xml', + 'wizard/mrp_workorder_batch_replan_wizard_views.xml', 'views/mrp_views_menus.xml', 'views/agv_scheduling_views.xml', 'views/stock_lot_views.xml', @@ -38,6 +39,8 @@ 'views/sf_maintenance_equipment.xml', 'views/res_config_settings_views.xml', 'views/sale_order_views.xml', + 'views/mrp_workorder_batch_replan.xml', + 'views/purchase_order_view.xml', ], 'assets': { @@ -50,6 +53,8 @@ 'sf_manufacturing/static/src/scss/kanban_change.scss', 'sf_manufacturing/static/src/xml/button_show_on_tree.xml', 'sf_manufacturing/static/src/js/workpiece_delivery_wizard_confirm.js', + 'sf_manufacturing/static/src/js/agv_scheduling_resend_confirm.js', + 'sf_manufacturing/static/src/js/agv_scheduling_cancel_confirm.js', 'sf_manufacturing/static/src/js/qr.js', 'sf_manufacturing/static/src/xml/qr.xml', ] diff --git a/sf_manufacturing/controllers/main.py b/sf_manufacturing/controllers/main.py index e45265d3..839b87fe 100644 --- a/sf_manufacturing/controllers/main.py +++ b/sf_manufacturing/controllers/main.py @@ -27,7 +27,7 @@ class JikimoSaleRoutePicking(Sf_Bf_Connect): bfm_process_order_list = json.loads(kw['bfm_process_order_list']) order_id = request.env['sale.order'].with_user(request.env.ref("base.user_admin")).sale_order_create( company_id, kw['delivery_name'], kw['delivery_telephone'], kw['delivery_address'], - kw['delivery_end_date'], kw['payments_way'], kw['pay_way'], state='draft') + kw['delivery_end_date'], kw['payments_way'], kw['pay_way'], kw['order_number'], state='draft') i = 1 # 给sale_order的default_code字段赋值 # aa = request.env['sale.order'].sudo().search([('name', '=', order_id.name)]) diff --git a/sf_manufacturing/i18n/zh_CN.po b/sf_manufacturing/i18n/zh_CN.po new file mode 100644 index 00000000..a3e8e67e --- /dev/null +++ b/sf_manufacturing/i18n/zh_CN.po @@ -0,0 +1,21 @@ +#. module: sf_manufacturing +#. odoo-python +#: code:addons/sf_manufacturing/models/mrp_production.py:0 +#, python-format +msgid "You must enter a serial number for %s" +msgstr "您必须为%s输入一个序列号。" + + +#. module: sf_manufacturing +#. odoo-python +#: code:addons/sf_manufacturing/models/mrp_production.py:0 +#, python-format +msgid "You must enter a serial number for each line of %s" +msgstr "您必须为以下各%s行输入序列号" + +#. module: sf_manufacturing +#. odoo-python +#: code:addons/sf_manufacturing/models/mrp_production.py:0 +#, python-format +msgid "You have %s incomplete supplies: %s" +msgstr "您有%s补给未完成: %s" \ No newline at end of file diff --git a/sf_manufacturing/models/__init__.py b/sf_manufacturing/models/__init__.py index 9f77d841..c4d8ad94 100644 --- a/sf_manufacturing/models/__init__.py +++ b/sf_manufacturing/models/__init__.py @@ -15,3 +15,4 @@ from . import sf_technology_design from . import sf_production_common from . import sale_order from . import quick_easy_order +from . import purchase_order \ No newline at end of file diff --git a/sf_manufacturing/models/agv_scheduling.py b/sf_manufacturing/models/agv_scheduling.py index 868964d5..f39c07e7 100644 --- a/sf_manufacturing/models/agv_scheduling.py +++ b/sf_manufacturing/models/agv_scheduling.py @@ -1,6 +1,7 @@ import logging import requests +from datetime import timedelta from odoo import models, fields, api, _ from odoo.exceptions import UserError @@ -210,9 +211,18 @@ class AgvScheduling(models.Model): def button_cancel(self): # 弹出二次确认窗口后执行 for rec in self: - if rec.state != '待下发': - raise UserError('只有待下发状态的AGV调度任务才能取消!') rec.state = '已取消' + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'target': 'new', + 'params': { + 'message': '任务取消成功!', + 'type': 'success', + 'sticky': False, + 'next': {'type': 'ir.actions.act_window_close'}, + } + } def finish_scheduling(self): """ @@ -232,7 +242,7 @@ class AgvScheduling(models.Model): agv_route sf.agv.task.route对象 """ for rec in self: - if rec.state != '待下发': + if rec.state not in ['待下发', '配送中']: return False _logger.info('AGV任务调度:下发调度任务,路线为%s' % agv_task_route) rec.state = '配送中' @@ -264,7 +274,45 @@ class AgvScheduling(models.Model): 'task_delivery_time': fields.Datetime.now() }) return super().write(vals) + + def button_cancel_confirm(self): + if self.task_delivery_time > fields.Datetime.now() - timedelta(minutes=10): + return { + 'type': 'ir.actions.client', + 'tag': 'agv_scheduling_cancel_confirm', + 'params': { + 'agv_scheduling_id': self.id, + } + } + else: + return self.button_cancel() + def button_resend_confirm(self): + if self.task_delivery_time > fields.Datetime.now() - timedelta(minutes=10): + return { + 'type': 'ir.actions.client', + 'tag': 'agv_scheduling_resend_confirm', + 'params': { + 'agv_scheduling_id': self.id, + 'context': self.env.context, + } + } + else: + return self.button_resend() + + def button_resend(self): + self.dispatch_scheduling(self.agv_route_id) + return { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'target': 'new', + 'params': { + 'message': '任务重新下发成功!', + 'type': 'success', + 'sticky': False, + 'next': {'type': 'ir.actions.act_window_close'}, + } + } class ResMrpWorkOrder(models.Model): _inherit = 'mrp.workorder' diff --git a/sf_manufacturing/models/model_type.py b/sf_manufacturing/models/model_type.py index 1fe36b90..601c34e8 100644 --- a/sf_manufacturing/models/model_type.py +++ b/sf_manufacturing/models/model_type.py @@ -75,7 +75,7 @@ class ManualProductModelTypeRoutingSort(models.Model): _description = '成品工序排序(人工线下加工)' sequence = fields.Integer('Sequence') - route_workcenter_id = fields.Many2one('mrp.routing.workcenter') + route_workcenter_id = fields.Many2one('mrp.routing.workcenter', domain=[('routing_type', 'in', ['人工线下加工'])]) is_repeat = fields.Boolean('重复', related='route_workcenter_id.is_repeat') routing_type = fields.Selection(string="工序类型", related='route_workcenter_id.routing_type') workcenter_ids = fields.Many2many('mrp.workcenter', required=False, related='route_workcenter_id.workcenter_ids') diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index f11a32ac..af0f0aa9 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -8,6 +8,7 @@ import re 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.addons.sf_base.commons.common import Common @@ -18,6 +19,7 @@ class MrpProduction(models.Model): _inherit = 'mrp.production' _description = "制造订单" _order = 'create_date desc' + sale_order_id = fields.Many2one('sale.order', string='销售订单', compute='_compute_sale_order_id', store=True) deadline_of_delivery = fields.Date('订单交期', tracking=True, compute='_compute_deadline_of_delivery') # tray_ids = fields.One2many('sf.tray', 'production_id', string="托盘") maintenance_count = fields.Integer(compute='_compute_maintenance_count', string="Number of maintenance requests") @@ -34,6 +36,29 @@ class MrpProduction(models.Model): tool_state_remark = fields.Text(string='功能刀具状态备注(缺刀)', compute='_compute_tool_state_remark', store=True) tool_state_remark2 = fields.Text(string='功能刀具状态备注(无效刀)', readonly=True) + @api.depends('procurement_group_id.mrp_production_ids.move_dest_ids.group_id.sale_id') + def _compute_sale_order_id(self): + for production in self: + # 初始化 sale_order_id 为 False + sale_order_id = False + # 使用正则表达式查找产品名称中的 'S' 开头的字母数字字符串 + match = re.search(r'S\d+', production.product_id.with_context(lang='zh_CN').name) # 从字符串开始匹配 + + if match: + result = match.group(0) + try: + # 查找与匹配的字符串相符的销售订单 + sale_order = self.env['sale.order'].search( + [('name', '=', result)], limit=1, order='id asc' + ) + if sale_order: + production.sale_order_id = sale_order.id + else: + logging.warning("No sale order found for production {} with product {} (name match: {})".format( + production.id, production.product_id.name, result)) + except Exception as e: + logging.error("Error while fetching sale order for production {}: {}".format(production.id, str(e))) + @api.depends('procurement_group_id.mrp_production_ids.move_dest_ids.group_id.sale_id') def _compute_deadline_of_delivery(self): for production in self: @@ -338,24 +363,18 @@ class MrpProduction(models.Model): # if production.state == 'pending_cam': # if all(wo_state in 'done' for wo_state in production.workorder_ids.mapped('state')): # production.state = 'done' - if any( - ( - wo.test_results == '返工' and wo.state == 'done' and production.programming_state in [ - '已编程']) or ( - wo.state == 'rework' and production.programming_state == '编程中') or ( - wo.is_rework is True and wo.state == 'done' and production.programming_state in ['编程中', - '已编程']) - for wo in - production.workorder_ids): + if any((wo.test_results == '返工' and wo.state == 'done' and production.programming_state in ['已编程']) + or (wo.is_rework is True and wo.state == 'done' and production.programming_state in ['编程中', '已编程']) + for wo in production.workorder_ids): production.state = 'rework' if any(wo.test_results == '报废' and wo.state == 'done' for wo in production.workorder_ids): production.state = 'scrap' if any(dr.test_results == '报废' and dr.handle_result == '已处理' for dr in production.detection_result_ids): production.state = 'cancel' - # 如果制造订单的功能刀具为【无效刀】则制造订单状态改为返工 - if production.tool_state == '2': - production.state = 'rework' + if production.workorder_ids and all(wo_state in ('done', 'rework', 'cancel') for wo_state in production.workorder_ids.mapped('state')): + if production.state not in ['scrap', 'rework', 'cancel']: + production.state = 'done' # 退回调整 def technology_back_adjust(self): @@ -378,22 +397,55 @@ class MrpProduction(models.Model): if process_parameters: raise UserError(_("【工艺设计】-【参数】为%s的在【产品】中不存在,请先创建", ", ".join(process_parameters))) if production_confirmed: - return { - 'name': _('退回调整'), - 'type': 'ir.actions.act_window', - 'view_mode': 'form', - 'res_model': 'sf.production.technology.re_adjust.wizard', - 'target': 'new', - 'context': { - 'default_production_id': self.id, - 'default_origin': self.origin, - }} + production_count = self.env['mrp.production'].search_count([ + ('origin', '=', self.origin), + ('product_id', '=', self.product_id.id), + ('state', '=', 'confirmed') + ]) + if production_count > 1: + return { + 'name': _('退回调整'), + 'type': 'ir.actions.act_window', + 'views': [(self.env.ref( + 'sf_manufacturing.sf_production_technology_re_adjust_wizard_form_view').id, + 'form')], + 'res_model': 'sf.production.technology.re_adjust.wizard', + 'target': 'new', + 'context': { + 'default_production_id': self.id, + 'default_origin': self.origin, + }} + else: + return { + 'name': _('退回调整'), + 'type': 'ir.actions.act_window', + 'views': [(self.env.ref( + 'sf_manufacturing.sf_production_technology_re_adjust_wizard_confirm_form_view').id, + 'form')], + 'res_model': 'sf.production.technology.re_adjust.wizard', + 'target': 'new', + 'context': { + 'default_production_id': self.id, + 'default_origin': self.origin, + }} # 工艺确认 def technology_confirm(self): process_parameters = [] account_moves = [] parameters_not = [] + # 获取原有的工单对应的工序 + origin_designs = self.workorder_ids.technology_design_id + # 获取已删除的工序 + deleted_designs = origin_designs - self.technology_design_ids + if deleted_designs: + for deleted_design in deleted_designs: + workorder = self.env['mrp.workorder'].search([('technology_design_id', '=', deleted_design.id)]) + purchase = workorder._get_surface_technics_purchase_ids() + account = self.env['account.move'].search([('id', 'in', purchase.invoice_ids.ids)]) + if account.state not in ['cancel', False]: + if purchase.name not in account_moves: + account_moves.append(purchase.name) special_design = self.technology_design_ids.filtered( lambda a: a.routing_tag == 'special' and a.is_auto is False) for special in special_design: @@ -405,11 +457,7 @@ class MrpProduction(models.Model): if not product_production_process: if special.process_parameters_id not in process_parameters: process_parameters.append(special.process_parameters_id.display_name) - purchase = self.env['purchase.order'].search([('origin', '=', special.production_id.name)]) - account = self.env['account.move'].search([('id', 'in', purchase.invoice_ids)]) - if account.state not in ['cancel', False]: - if purchase.name not in account_moves: - account_moves.append(purchase.name) + if account_moves: raise UserError(_("请联系工厂生产经理对采购订单为%s生成的账单进行取消", ", ".join(account_moves))) if parameters_not: @@ -441,16 +489,37 @@ class MrpProduction(models.Model): error_panel.append(design.panel) else: if not error_panel and not process_parameters: - return { - 'name': _('工艺确认'), - 'type': 'ir.actions.act_window', - 'view_mode': 'form', - 'res_model': 'sf.production.technology.wizard', - 'target': 'new', - 'context': { - 'default_production_id': self.id, - 'default_origin': self.origin, - }} + production_count = self.env['mrp.production'].search_count([ + ('origin', '=', self.origin), + ('product_id', '=', self.product_id.id), + ('state', '=', 'technology_to_confirmed') + ]) + if production_count > 1: + return { + 'name': _('工艺确认'), + 'type': 'ir.actions.act_window', + 'views': [(self.env.ref( + 'sf_manufacturing.sf_production_technology_wizard_form_view').id, + 'form')], + 'res_model': 'sf.production.technology.wizard', + 'target': 'new', + 'context': { + 'default_production_id': self.id, + 'default_origin': self.origin, + }} + else: + return { + 'name': _('工艺确认'), + 'type': 'ir.actions.act_window', + 'views': [(self.env.ref( + 'sf_manufacturing.sf_production_technology_wizard_confirm_form_view').id, + 'form')], + 'res_model': 'sf.production.technology.wizard', + 'target': 'new', + 'context': { + 'default_production_id': self.id, + 'default_origin': self.origin, + }} if error_panel: raise UserError(_("【加工面】为%s的标准工序顺序有误,请调整后重试", ", ".join(error_panel))) return True @@ -694,7 +763,8 @@ class MrpProduction(models.Model): }] if production.product_id.categ_id.type in ['成品', '坯料']: # # 根据工序设计生成工单 - for route in production.technology_design_ids: + technology_design_ids = sorted(production.technology_design_ids, key=lambda x: x.sequence) + for route in technology_design_ids: workorder_has = self.env['mrp.workorder'].search( [('technology_design_id', '=', route.id), ('production_id', '=', production.id)]) if not workorder_has: @@ -720,6 +790,7 @@ class MrpProduction(models.Model): groupby(production_all, key=lambda x: x.product_id.id)} for product_id, pd in grouped_product_ids.items(): product_id_to_production_names[product_id] = [p.name for p in pd] + sorted_workorders = None for production in production_all: proc_workorders = [] process_parameter_workorder = self.env['mrp.workorder'].search( @@ -731,49 +802,13 @@ class MrpProduction(models.Model): workorder._get_surface_technics_purchase_ids().write({'state': 'cancel'}) workorder.move_subcontract_workorder_ids.write({'state': 'cancel'}) workorder.move_subcontract_workorder_ids.picking_id.write({'state': 'cancel'}) - consecutive_workorders = [] sorted_workorders = sorted(process_parameter_workorder, key=lambda w: w.sequence) - for i, workorder in enumerate(sorted_workorders): - # 检查当前工作订单和下一个工作订单是否连续,并且供应商相同 - if i == 0: - consecutive_workorders.append(workorder) - elif workorder.sequence == sorted_workorders[ - i - 1].sequence + 1 and workorder.supplier_id.id == sorted_workorders[i - 1].supplier_id.id: - consecutive_workorders.append(workorder) - else: - # 处理连续组,如果它不为空 - if consecutive_workorders: - proc_workorders.append(consecutive_workorders) - # 创建外协出入库单和采购订单 - # self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, production, sorted_workorders) - # self.env['purchase.order'].get_purchase_order(consecutive_workorders, production, - # product_id_to_production_names) - if i < len(sorted_workorders) - 1: - # 重置连续组,并添加当前工作订单 - consecutive_workorders = [workorder] - else: - # 判断最后一笔: - if workorder.sequence == sorted_workorders[ - i - 1].sequence and workorder.supplier_id.id == sorted_workorders[ - i - 1].supplier_id.id: - consecutive_workorders = [workorder] - else: - proc_workorders.append([workorder]) - # 立即创建外协出入库单和采购订单 - # self.env['stock.picking'].create_outcontract_picking(workorder, production) - # self.env['purchase.order'].get_purchase_order(workorder, production, - # product_id_to_production_names) - consecutive_workorders = [] - - # 处理最后一个组,即使它可能只有一个工作订单 - if consecutive_workorders: - proc_workorders.append(consecutive_workorders) - # self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, production) - # self.env['purchase.order'].get_purchase_order(consecutive_workorders, production, - # product_id_to_production_names) - for workorders in reversed(proc_workorders): + if not sorted_workorders: + return + for workorders in reversed(sorted_workorders): self.env['stock.picking'].create_outcontract_picking(workorders, production, sorted_workorders) self.env['purchase.order'].get_purchase_order(workorders, production, product_id_to_production_names) + # 工单排序 def _reset_work_order_sequence1(self, k): for rec in self: @@ -887,10 +922,11 @@ class MrpProduction(models.Model): work_ids = workorder_ids.filtered(lambda item: item.sequence == 0) # 对工单进行逐个插入 for work_id in work_ids: - for order_id in rec.workorder_ids.filtered(lambda item: item.sequence > 0): - if work_id.name == order_id.name: - work_id.sequence = order_id.sequence + 1 - break + order_rework_ids = rec.workorder_ids.filtered( + lambda item: (item.sequence > 0 and work_id.name == item.name + and work_id.processing_panel == item.processing_panel)) + order_rework_ids = sorted(order_rework_ids, key=lambda item: item.sequence, reverse=True) + work_id.sequence = order_rework_ids[0].sequence + 1 # 对该工单之后的工单工序进行加一 work_order_ids = rec.workorder_ids.filtered( lambda item: item.sequence >= work_id.sequence and item.id != work_id.id) @@ -905,6 +941,8 @@ class MrpProduction(models.Model): and item.process_parameters_id == work.surface_technics_parameters_id) or (item.route_id.name == work.name and item.panel and item.panel == work.processing_panel)) + if work.name == '人工线下加工': + td_ids = technology_design_ids.filtered(lambda item: (item.route_id.name in work.name)) if td_ids: work.sequence = td_ids[0].sequence cancel_work_ids = workorder_ids.filtered(lambda item: item.state in ('已取消', 'cancel')) @@ -1170,7 +1208,9 @@ class MrpProduction(models.Model): lambda wk: (wk.name == result_id.routing_type and wk.processing_panel == result_id.processing_panel and wk.state == 'done')).id for result_id in result_ids] - + workorder_ids = self.workorder_ids.filtered( + lambda wk: wk.technology_design_id.routing_tag == 'standard' and wk.state not in ['rework', 'cancel']) + logging.info('标准工艺工单【%s】' % workorder_ids) return { 'name': _('返工'), 'type': 'ir.actions.act_window', @@ -1179,7 +1219,7 @@ class MrpProduction(models.Model): 'target': 'new', 'context': { 'default_production_id': self.id, - 'default_workorder_ids': self.workorder_ids.ids, + 'default_workorder_ids': workorder_ids.ids if workorder_ids.ids != [] else self.workorder_ids.ids, 'default_hidden_workorder_ids': ','.join(map(str, work_id_list)) if work_id_list != [] else '', 'default_reprogramming_num': cloud_programming['reprogramming_num'], 'default_programming_state': cloud_programming['programming_state'], @@ -1435,6 +1475,26 @@ class MrpProduction(models.Model): for production in self: production.production_type = '自动化产线加工' if not production.product_id.is_manual_processing else '人工线下加工' + @api.depends('procurement_group_id.mrp_production_ids.move_dest_ids.group_id.sale_id') + def _compute_sale_order_count(self): + for production in self: + if production.sale_order_id: + production.sale_order_count = 1 + else: + production.sale_order_count = 0 + + def action_view_sale_orders(self): + if self.sale_order_id: + action = { + 'res_model': 'sale.order', + 'type': 'ir.actions.act_window', + } + action.update({ + 'view_mode': 'form', + 'res_id': self.sale_order_id.id, + }) + return action + @api.model_create_multi def create(self, vals_list): """ @@ -1495,7 +1555,9 @@ class MrpProduction(models.Model): def action_view_purchase_orders(self): self.ensure_one() - if self.product_id.product_tmpl_id.single_manufacturing == True: + if self.is_remanufacture: + production = self + elif self.product_id.product_tmpl_id.single_manufacturing == True: production = self.env['mrp.production'].search( [('origin', '=', self.origin), ('product_id', '=', self.product_id.id)], limit=1, order='id asc') else: @@ -1519,6 +1581,26 @@ class MrpProduction(models.Model): }) return action + def _subcontract_sanity_check(self): + for production in self: + if production.product_tracking != 'none' and not self.lot_producing_id: + raise UserError(_('You must enter a serial number for %s') % production.product_id.name) + for sml in production.move_raw_ids.move_line_ids: + if sml.tracking != 'none' and not sml.lot_id: + picking_ids = production.picking_ids.filtered( + lambda p: p.state not in ['done', 'cancel']) + picking_num = len(picking_ids) + picking_info = ', '.join( + ['%s:%s' % (picking.picking_type_id.name, picking.name) for picking in picking_ids] + ) + if picking_info: + raise UserError(_('You have %s incomplete supplies: %s') % ( + picking_num, picking_info)) + else: + raise UserError( + _('You must enter a serial number for each line of %s') % sml.product_id.display_name) + return True + class sf_detection_result(models.Model): _name = 'sf.detection.result' diff --git a/sf_manufacturing/models/mrp_routing_workcenter.py b/sf_manufacturing/models/mrp_routing_workcenter.py index e28bd091..27c8e9d2 100644 --- a/sf_manufacturing/models/mrp_routing_workcenter.py +++ b/sf_manufacturing/models/mrp_routing_workcenter.py @@ -94,4 +94,10 @@ class ResMrpRoutingWorkcenter(models.Model): route_ids.append(t.route_id.surface_technics_id.id) domain = [('id', 'not in', route_ids), ('routing_tag', '=', 'special')] return self._search(domain, limit=limit, access_rights_uid=name_get_uid) + if self._context.get('is_duplicate') and self._context.get('model_name'): + # 查询出已经选择的工序 + model_type = self.env[self._context.get('model_name')].search_read([],['route_workcenter_id']) + route_workcenter_ids = [item['route_workcenter_id'][0] if item['route_workcenter_id'] else False for item in model_type] + domain = args + [('id', 'not in', route_workcenter_ids)] + return self._search(domain, limit=limit, access_rights_uid=name_get_uid) return super()._name_search(name, args, operator, limit, name_get_uid) diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index bc012ab9..b1f56ca7 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -17,6 +17,7 @@ from odoo.exceptions import UserError, ValidationError from odoo.addons.sf_mrs_connect.models.ftp_operate import FtpController + class ResMrpWorkOrder(models.Model): _inherit = 'mrp.workorder' _order = 'sequence asc' @@ -36,7 +37,7 @@ class ResMrpWorkOrder(models.Model): store=True, check_company=True, string="材料") product_tmpl_id_materials_type_id = fields.Many2one(related='production_id.product_tmpl_id.materials_type_id', readonly=True, store=True, check_company=True, string="型号") - workcenter_id = fields.Many2one('mrp.workcenter', string='工作中心', required=False) + # workcenter_id = fields.Many2one('mrp.workcenter', string='工作中心', required=False) users_ids = fields.Many2many("res.users", 'users_workorder', related="workcenter_id.users_ids") processing_panel = fields.Char('加工面') sequence = fields.Integer(string='工序') @@ -129,7 +130,7 @@ class ResMrpWorkOrder(models.Model): Y10_axis = fields.Float(default=0) Z10_axis = fields.Float(default=0) X_deviation_angle = fields.Integer(string="X轴偏差度", default=0) - test_results = fields.Selection([("合格", "合格")], default='合格', + test_results = fields.Selection([("合格", "合格"), ("返工", "返工")], default='合格', string="检测结果", tracking=True) cnc_ids = fields.One2many("sf.cnc.processing", 'workorder_id', string="CNC加工程序") cmm_ids = fields.One2many("sf.cmm.program", 'workorder_id', string="CMM程序") @@ -138,7 +139,8 @@ 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='外协出入库单', compute='_compute_surface_technics_picking_ids') + picking_ids = fields.Many2many('stock.picking', string='外协出入库单', + compute='_compute_surface_technics_picking_ids') purchase_id = fields.Many2many('purchase.order', string='外协采购单') surface_technics_picking_count = fields.Integer("外协出入库", compute='_compute_surface_technics_picking_ids') @@ -327,9 +329,9 @@ class ResMrpWorkOrder(models.Model): 'view_mode': 'form', } return result - + def _get_surface_technics_purchase_ids(self): - domain = [('origin', '=', self.production_id.name), ('purchase_type', '=', 'consignment')] + domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment')] purchase_orders = self.env['purchase.order'].search(domain) purchase_orders_id = self.env['purchase.order'] for po in purchase_orders: @@ -1034,49 +1036,6 @@ class ResMrpWorkOrder(models.Model): 'production_id.tool_state', 'production_id.schedule_state', 'sequence', 'production_id.programming_state') def _compute_state(self): - # super()._compute_state() - # for workorder in self: - # if workorder.sequence != 1: - # previous_workorder = self.env['mrp.workorder'].search( - # [('production_id', '=', workorder.production_id.id), - # ('sequence', '=', workorder.sequence - 1)]) - # if workorder.state == 'pending': - # if all([wo.state in ('done', 'cancel') for wo in workorder.blocked_by_workorder_ids]): - # if workorder.production_id.reservation_state == 'assigned' and workorder.production_id.schedule_state == '已排': - # if ((workorder.sequence == 1 and not workorder.blocked_by_workorder_ids) - # or (workorder.blocked_by_workorder_ids.state in ('done', 'cancel') - # and workorder.blocked_by_workorder_ids.test_results not in ['报废', '返工']) - # or (previous_workorder.state in ('done', 'cancel') - # and not workorder.blocked_by_workorder_ids - # and previous_workorder.test_results not in ['报废', '返工']) - # ): - # workorder.state = 'ready' - # continue - # if workorder.production_id.schedule_state == '未排' and workorder.state in ('waiting', 'ready'): - # if workorder.sequence != 1: - # workorder.state = 'pending' - # continue - # if workorder.state not in ('waiting', 'ready'): - # continue - # if workorder.state in ( - # 'waiting') and workorder.sequence == 1 and workorder.production_id.schedule_state == '已排': - # workorder.state = 'ready' - # continue - # if not all([wo.state in ('done', 'cancel') for wo in workorder.blocked_by_workorder_ids]): - # workorder.state = 'pending' - # if workorder.state in ['waiting']: - # if previous_workorder.state == 'waiting': - # workorder.state = 'pending' - # if workorder.sequence == 1 and workorder.state == 'pending': - # workorder.state = 'waiting' - # continue - # if workorder.production_id.reservation_state not in ('waiting', 'confirmed', 'assigned'): - # continue - # if workorder.production_id.reservation_state == 'assigned' and workorder.state == 'waiting' and workorder.production_id.schedule_state == '已排': - # workorder.state = 'ready' - # elif workorder.production_id.reservation_state != 'assigned' and workorder.state == 'ready': - # workorder.state = 'waiting' - for workorder in self: # 如果工单的工序没有进行排序则跳出循环 if workorder.production_id.workorder_ids.filtered(lambda wk: wk.sequence == 0): @@ -1110,13 +1069,11 @@ class ResMrpWorkOrder(models.Model): continue else: workorder.state = 'ready' - continue + continue # ================= 如果制造订单刀具状态为[无效刀、缺刀] 或者 制造订单状态为[返工]========================== if (workorder.production_id.tool_state in ['1', '2'] or workorder.production_id.state == 'rework' or workorder.production_id.schedule_state != '已排' - or workorder.production_id.reservation_state not in ['assigned'] - or workorder.production_id.workorder_ids.filtered( - lambda wk: wk.sequence == workorder.sequence - 1).test_results in ['报废', '返工']): + or workorder.production_id.reservation_state not in ['assigned']): if workorder.state != 'waiting': workorder.state = 'waiting' continue @@ -1135,86 +1092,12 @@ class ResMrpWorkOrder(models.Model): if workorder.is_subcontract is False: workorder.state = 'ready' else: - # production_programming = self.env['mrp.production'].search( - # [('origin', '=', self.production_id.origin)], order='name asc') - # production_no_remanufacture = production_programming.filtered( - # lambda a: a.is_remanufacture is False) - # production_list = [production.name for production in production_programming] - # purchase_orders = self.env['purchase.order'].search( - # [('origin', 'ilike', ','.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_no_remanufacture)): - # if all(pur_order.state == 'purchase' for pur_order in purchase_orders): - # workorder.state = 'ready' - # else: - # workorder.state = 'waiting' purchase_orders_id = self._get_surface_technics_purchase_ids() if purchase_orders_id: workorder.state = 'ready' if purchase_orders_id.state == 'purchase' else 'waiting' else: workorder.state = 'waiting' - # re_work = self.env['mrp.workorder'].search([('production_id', '=', workorder.production_id.id), - # ('processing_panel', '=', workorder.processing_panel), - # ('is_rework', '=', True), ('state', 'in', ['done', 'rework'])]) - # cnc_workorder = self.env['mrp.workorder'].search( - # [('production_id', '=', workorder.production_id.id), - # ('processing_panel', '=', workorder.processing_panel), - # ('routing_type', '=', 'CNC加工'), ('state', 'in', ['done', 'rework']), - # ('test_results', '=', '返工')]) - # cnc_workorder_pending = self.env['mrp.workorder'].search( - # [('production_id', '=', workorder.production_id.id), - # ('processing_panel', '=', workorder.processing_panel), - # ('routing_type', '=', 'CNC加工'), ('state', 'in', ['pending'])]) - # unclamp_workorder = self.env['mrp.workorder'].search( - # [('production_id', '=', workorder.production_id.id), - # ('sequence', '=', workorder.sequence - 1), - # ('state', 'in', ['done'])]) - # if workorder.state not in ['cancel', 'progress', 'rework']: - # if workorder.production_id.state == 'rework': - # if workorder.routing_type == '装夹预调': - # # # 有返工工单 - # # if re_work: - # # 新工单 - # if workorder.is_rework is False: - # if (workorder.production_id.programming_state == '已编程' - # and workorder.production_id.is_rework is False): - # if re_work or cnc_workorder: - # workorder.state = 'ready' - # else: - # if workorder.production_id.is_rework is True: - # if re_work or cnc_workorder: - # workorder.state = 'waiting' - # - # elif workorder.routing_type == 'CNC加工': - # pre_workorder = self.env['mrp.workorder'].search( - # [('production_id', '=', workorder.production_id.id), - # ('processing_panel', '=', workorder.processing_panel), - # ('routing_type', '=', '装夹预调'), ('state', '=', 'done')]) - # if pre_workorder: - # if re_work: - # workorder.state = 'waiting' - # elif workorder.routing_type == '解除装夹': - # if cnc_workorder: - # if not cnc_workorder_pending or unclamp_workorder.test_results == '报废': - # workorder.state = 'waiting' - # # else: - # # if workorder.production_id.is_rework is True: - # # workorder.state = 'waiting' - # elif workorder.production_id.state == 'progress': - # if (workorder.routing_type == '装夹预调' and workorder.production_id.programming_state == '已编程' - # and workorder.is_rework is False and workorder.state not in ['done', 'rework', 'cancel']): - # if workorder.production_id.is_rework is False: - # if re_work or cnc_workorder or unclamp_workorder: - # workorder.state = 'ready' - # # if (re_work or cnc_workorder) and workorder.production_id.is_rework is False: - # # workorder.state = 'ready' - # elif workorder.production_id.state == 'scrap': - # if workorder.routing_type == '解除装夹' and unclamp_workorder.test_results == '报废': - # workorder.state = 'waiting' - # 重写工单开始按钮方法 def button_start(self): # 判断工单状态是否为等待组件 @@ -1425,13 +1308,14 @@ class ResMrpWorkOrder(models.Model): len(done_workorder) == len(record.production_id.workorder_ids)): is_production_id = True if record.routing_type in ['解除装夹'] or ( - record.is_rework is True and record.routing_type in ['装夹预调']) or ( - record.test_results in ['返工', '报废'] and record.routing_type in ['CNC加工']): + record.is_rework is True and record.routing_type in ['装夹预调']): for workorder in record.production_id.workorder_ids: if workorder.processing_panel == record.processing_panel: rfid_code = workorder.rfid_code workorder.write({'rfid_code_old': rfid_code, 'rfid_code': False}) + self.env['stock.lot'].sudo().search([('rfid', '=', rfid_code)]).write( + {'tool_material_status': '可用'}) if workorder.rfid_code: raise ValidationError(f'【{workorder.name}】工单解绑失败,请重新点击完成按钮!!!') # workorder.rfid_code_old = rfid_code @@ -1456,6 +1340,9 @@ class ResMrpWorkOrder(models.Model): # 解绑托盘 def unbind_tray(self): + for item in self: + self.env['stock.lot'].sudo().search([('rfid', '=', item.rfid_code)]).write( + {'tool_material_status': '可用'}) self.production_id.workorder_ids.write({ 'rfid_code': False, 'tray_serial_number': False, @@ -1570,7 +1457,7 @@ class ResMrpWorkOrder(models.Model): 'default_confirm_button': '确认解除', # 'default_feeder_station_start_id': feeder_station_start_id, }} - + move_subcontract_workorder_ids = fields.One2many('stock.move', 'subcontract_workorder_id', string='组件') @@ -1781,6 +1668,7 @@ class SfWorkOrderBarcodes(models.Model): if workorder_rfid: for item in workorder_rfid: item.write({'rfid_code': barcode}) + lot.sudo().write({'tool_material_status': '在用'}) logging.info("Rfid[%s]绑定成功!!!" % barcode) else: raise UserError('该Rfid【%s】绑定的是【%s】, 不是托盘!!!' % (barcode, lot.product_id.name)) diff --git a/sf_manufacturing/models/product_template.py b/sf_manufacturing/models/product_template.py index 460fd994..4b927596 100644 --- a/sf_manufacturing/models/product_template.py +++ b/sf_manufacturing/models/product_template.py @@ -774,11 +774,10 @@ class ResProductMo(models.Model): # bfm下单 manual_quotation = fields.Boolean('人工编程', default=False, readonly=True) - part_number = fields.Char(string='零件图号', readonly=True) machining_drawings = fields.Binary('2D加工图纸', readonly=True) quality_standard = fields.Binary('质检标准', readonly=True) part_name = fields.Char(string='零件名称', readonly=True) - + part_number = fields.Char(string='零件图号', readonly=True) @api.constrains('tool_length') def _check_tool_length_size(self): if self.tool_length > 1000000: @@ -892,7 +891,7 @@ class ResProductMo(models.Model): 'machining_drawings': '' if not item['machining_drawings'] else base64.b64decode( item['machining_drawings']), 'quality_standard': '' if not item['quality_standard'] else base64.b64decode(item['quality_standard']), - 'part_name': item['part_name'], + 'part_name': item.get('part_name') or '', } tax_id = self.env['account.tax'].sudo().search( [('type_tax_use', '=', 'sale'), ('amount', '=', item.get('tax')), ('price_include', '=', 'True')]) diff --git a/sf_manufacturing/models/purchase_order.py b/sf_manufacturing/models/purchase_order.py new file mode 100644 index 00000000..2813883e --- /dev/null +++ b/sf_manufacturing/models/purchase_order.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from collections import defaultdict + +from odoo import api, fields, models, _ +from odoo.tools import OrderedSet + + +# _get_surface_technics_purchase_ids +class PurchaseOrder(models.Model): + _inherit = 'purchase.order' + + production_count = fields.Integer( + "关联制造订单", + compute='_compute_workorder_count', + ) + + def action_view_production(self): + origins = [order.name for order in self.picking_ids] + production_id = self.env['mrp.production'].search([('origin', 'in', origins)]) + if not production_id: + return + action = { + 'res_model': 'mrp.production', + 'type': 'ir.actions.act_window', + } + if len(production_id) == 1: + action.update({ + 'view_mode': 'form', + 'res_id': production_id.id, + }) + else: + action.update({ + 'name': _("制造订单列表"), + 'domain': [('id', 'in', production_id.ids)], + 'view_mode': 'tree,form', + }) + return action + + def _compute_workorder_count(self): + for purchase in self: + origins = [order.name for order in purchase.picking_ids] + production_id = self.env['mrp.production'].search([('origin', 'in', origins)]) + purchase.production_count = len(production_id) + + def button_confirm(self): + super().button_confirm() + workorders = self.env['mrp.workorder'].search([('purchase_id', '=', self.id), ('state', '!=', 'cancel')]) + for workorder in workorders: + if workorder.routing_type == '表面工艺' and workorder.is_subcontract is True: + move_out = workorder.move_subcontract_workorder_ids[1] + 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(workorder.production_id, workorder)) + return True + + origin_sale_id = fields.Many2one('sale.order', string='销售订单号', store=True, compute='_compute_origin_sale_id') + origin_sale_ids = fields.Many2many('sale.order', string='销售订单号(多个)', store=True, + compute='_compute_origin_sale_id') + + @api.depends('origin') + def _compute_origin_sale_id(self): + for purchase in self: + if not purchase.origin: + continue + elif 'MO' in purchase.origin: + mp_name_list = [name.strip() for name in purchase['origin'].split(',')] + os_ids = list({mp_id.sale_order_id.id for mp_id in self.env['mrp.production'].sudo().search([ + ('name', 'in', mp_name_list)])}) + if len(os_ids) == 1: + purchase.origin_sale_id = os_ids[0] + elif len(os_ids) >= 2: + purchase.origin_sale_ids = os_ids + elif 'S' in purchase.origin: + os_name_list = [name.strip() for name in purchase['origin'].split(',')] + os_ids = self.env['sale.order'].sudo().search([('name', 'in', os_name_list)]) + if len(os_ids) == 1: + purchase.origin_sale_id = os_ids.id + elif len(os_ids) >= 2: + purchase.origin_sale_ids = os_ids.ids + elif 'IN' in purchase.origin: + sp_name_list = [name.strip() for name in purchase['origin'].split(',')] + os_ids = list({sp_id.sale_order_id.id for sp_id in self.env['stock.picking'].sudo().search([ + ('name', 'in', sp_name_list)])}) + if len(os_ids) == 1: + purchase.origin_sale_id = os_ids[0] + elif len(os_ids) >= 2: + purchase.origin_sale_ids = os_ids + + +class PurchaseOrderLine(models.Model): + _inherit = 'purchase.order.line' + part_number = fields.Char('零件图号', related='product_id.part_number', readonly=True) + related_product = fields.Many2one('product.product', compute='_compute_related_product', string='关联产品', + help='经此产品工艺加工成的成品') + + @api.depends('order_id.origin') + def _compute_related_product(self): + for record in self: + if record.product_id.detailed_type: + production_id = self.env['mrp.production'].search([('name', '=', record.order_id.origin)]) + record.related_product = production_id.product_id if production_id else False + else: + record.related_product = False diff --git a/sf_manufacturing/models/sale_order.py b/sf_manufacturing/models/sale_order.py index a56721a2..e31faf1c 100644 --- a/sf_manufacturing/models/sale_order.py +++ b/sf_manufacturing/models/sale_order.py @@ -13,6 +13,9 @@ class SaleOrder(models.Model): ('sent', "报价已发送"), ('supply method', "供货方式待确认"), ('sale', "销售订单"), + ('processing', "加工中"), + ('physical_distribution', "物流中"), + ('delivered', "已交付"), ('done', "已锁定"), ('cancel', "已取消"), ]) @@ -149,7 +152,7 @@ class SaleOrder(models.Model): class SaleOrderLine(models.Model): _inherit = 'sale.order.line' - + part_number = fields.Char('零件图号', related='product_id.part_number', readonly=True) # 供货方式 supply_method = fields.Selection([ ('automation', "自动化产线加工"), diff --git a/sf_manufacturing/models/sf_technology_design.py b/sf_manufacturing/models/sf_technology_design.py index 392aa021..826c147d 100644 --- a/sf_manufacturing/models/sf_technology_design.py +++ b/sf_manufacturing/models/sf_technology_design.py @@ -1,4 +1,6 @@ # -*- coding: utf-8 -*- +from collections import Counter + from odoo import fields, models, api, _ from odoo.exceptions import ValidationError @@ -6,7 +8,7 @@ from odoo.exceptions import ValidationError class sf_technology_design(models.Model): _name = 'sf.technology.design' _description = "工艺设计" - + group_uniq_id = fields.Integer('同一制造订单唯一id',default=0) sequence = fields.Integer('序号') route_id = fields.Many2one('mrp.routing.workcenter', '工序') process_parameters_id = fields.Many2one('sf.production.process.parameter', string='表面工艺参数') @@ -17,6 +19,11 @@ class sf_technology_design(models.Model): is_auto = fields.Boolean('是否自动生成', default=False) active = fields.Boolean('有效', default=True) + # @api.depends('production_id') + # def _compute_group_uniq_id(self): + # for record in self: + + def json_technology_design_str(self, k, route, i, process_parameter): workorders_values_str = [0, '', { 'route_id': route.id if route.routing_type in ['表面工艺'] else route.route_workcenter_id.id, @@ -28,6 +35,8 @@ class sf_technology_design(models.Model): 'is_auto': True}] return workorders_values_str + def write(self, vals): + return super(sf_technology_design, self).write(vals) def unlink_technology_design(self): self.active = False @@ -37,4 +46,64 @@ class sf_technology_design(models.Model): for vals in vals_list: if not vals.get('route_id'): raise ValidationError(_("工序不能为空")) - return super(sf_technology_design, self).create(vals_list) + result = super(sf_technology_design, self).create(vals_list) + for res in result: + record = self.search([('production_id', '=', res.production_id.id), ('active', 'in', [True, False])], order='group_uniq_id desc', limit=1) + res.group_uniq_id=record.group_uniq_id + 1 + return result + def get_duplicates_with_inactive(self,technology_designs): + # 统计每个 'sequence' 出现的次数 + sequence_count = Counter(technology_design.sequence for technology_design in technology_designs) + + # 筛选出 'sequence' 重复且 'active' 为 False 的元素 + result = [ + technology_design for technology_design in technology_designs + if sequence_count[technology_design.sequence] > 1 and technology_design.active is False + ] + + return result + # def rearrange_numbering(self,self_technology_designs): + # inactive_designs = self.get_duplicates_with_inactive(self_technology_designs) + # if inactive_designs: + # max_design = max(self_technology_designs, key=lambda x: x.sequence) + # max_sequence = max_design.sequence if max_design else 0 + # for designs in inactive_designs: + # max_sequence += 1 + # designs.sequence = max_sequence + # self_technology_designs.sorted(key=lambda techology_design:techology_design.sequence) + # return self_technology_designs + + def get_technology_design(self): + return { + 'sequence':self.sequence, + 'route_id': self.route_id.id, + 'process_parameters_id': self.process_parameters_id.id, + 'panel': self.panel, + 'routing_tag': self.routing_tag, + 'time_cycle_manual': self.time_cycle_manual, + 'is_auto': self.is_auto, + 'active': self.active, + 'group_uniq_id':self.group_uniq_id, + } + def sync_technology_designs(self,production_technology_designs, self_technology_designs): + production_id = production_technology_designs[0].production_id.id + self_technology_design_dict = {item.group_uniq_id:item for item in self_technology_designs} + production_technology_designs_dict = {item.group_uniq_id:item for item in production_technology_designs} + for technology_design in production_technology_designs: + if not self_technology_design_dict.get(technology_design.group_uniq_id): + technology_design.write({'production_id': False}) + else: + technology_design.write(self_technology_design_dict.get(technology_design.group_uniq_id).get_technology_design()) + for technology_design in self_technology_designs: + if not production_technology_designs_dict.get(technology_design.group_uniq_id): + technology_design = technology_design.get_technology_design() + technology_design.update({'production_id': production_id}) + technology_design.pop('group_uniq_id') + self.env['sf.technology.design'].create(technology_design) + + + + def unified_procedure_multiple_work_orders(self,self_technology_designs,production_item): + technology_designs = self.env['sf.technology.design'].sudo().search( + [('production_id', '=', production_item.id), ('active', 'in', [True, False])]) + self.sync_technology_designs(self_technology_designs=self_technology_designs,production_technology_designs=technology_designs) \ No newline at end of file diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index 01c7bd93..a9b08513 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -588,40 +588,34 @@ class StockPicking(models.Model): address_of_delivery = fields.Char('联系地址', compute='_compute_move_ids', store=True) retrospect_ref = fields.Char('追溯参考', compute='_compute_move_ids', store=True) - + sale_order_id = fields.Many2one('sale.order', '销售单号', compute='_compute_move_ids', store=True) picking_type_sequence_code = fields.Char(related='picking_type_id.sequence_code') @api.depends('move_ids', 'move_ids.product_id') def _compute_move_ids(self): for item in self: if item.move_ids: - if item.picking_type_id.sequence_code == 'DL': - sale_name = item.move_ids[0].product_id.name.split('-')[1] - if 'S' in sale_name: - sale_id = self.env['sale.order'].sudo().search([('name', '=', sale_name)]) - item.person_of_delivery = sale_id.person_of_delivery - item.telephone_of_delivery = sale_id.telephone_of_delivery - item.address_of_delivery = sale_id.address_of_delivery + product_id = item.move_ids[0].product_id + if product_id: + sale_info = None + if product_id.categ_id.type == '坯料' and product_id.name.startswith('R-S'): + parts = product_id.name.split('-') + if len(parts) >= 3: + sale_name = parts[1] + sale_info = self.env['sale.order'].sudo().search( + [('name', '=', sale_name)]) else: - raise ValidationError('坯料名称格式错误,正确格式为[R-S???-?]!!!') - move_ids = [] - for move_id in item.move_ids: - move_ids.append(move_id.product_id.id) - boms = self.env['mrp.bom'].sudo().search([('bom_line_ids.product_id', 'in', move_ids)]) - if boms: - codes_list = [] - for bom in boms: - if bom.product_tmpl_id.default_code: - code_list = bom.product_tmpl_id.default_code.split('-') - if len(code_list) >= 4: - code = '-'.join(code_list[:4]) - if code not in codes_list: - codes_list.append(code) - else: - raise ValidationError('坯料成品的内部参考值格式错误') - item.retrospect_ref = ','.join(codes_list) - elif item.picking_type_id.sequence_code in ['INT', 'PC']: - pass + sale_order_line = self.env['sale.order.line'].sudo().search( + [('product_id', '=', product_id.id)]) + if sale_order_line: + sale_info = sale_order_line[0].order_id + if sale_info: + item.sale_order_id = sale_info.id + item.retrospect_ref = sale_info.order_code + if item.picking_type_id.sequence_code == 'DL': + item.person_of_delivery = sale_info.person_of_delivery + item.telephone_of_delivery = sale_info.telephone_of_delivery + item.address_of_delivery = sale_info.address_of_delivery # 设置外协出入单的名称 def _get_name_Res(self, rescode): @@ -644,58 +638,80 @@ class StockPicking(models.Model): workorder = move_in.subcontract_workorder_id workorders = workorder.production_id.workorder_ids subcontract_workorders = workorders.filtered(lambda wo: wo.is_subcontract == True).sorted('sequence') - if workorder == subcontract_workorders[-1]: + 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, + 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() + picking_type_out = self.env.ref('sf_manufacturing.outcontract_picking_out').id + if res and self.picking_type_id.id == picking_type_out: + move_out = self.move_ids + if move_out: + workorder = move_out.subcontract_workorder_id + workorder.button_start() + if self.location_id.name == '成品存货区' and self.location_dest_id.name == '客户': + sale_id = self.env['sale.order'].sudo().search( + [('name', '=', self.origin)]) + stock_picking_list = self.env['stock.picking'].sudo().search( + [('id', 'in', sale_id.picking_ids.ids)]) + stock_picking = stock_picking_list.filtered(lambda p: p.state not in ("done", "cancel")) + if sale_id and not stock_picking: + sale_id.write({'state': 'delivered'}) return res # 创建 外协出库入单 def create_outcontract_picking(self, workorders, item, sorted_workorders): for workorder in workorders: if workorder.move_subcontract_workorder_ids: - workorder.move_subcontract_workorder_ids.write({'state': 'draft'}) - workorder.move_subcontract_workorder_ids.picking_id.write({'state': 'draft'}) + workorder.move_subcontract_workorder_ids.write({'state': 'cancel'}) + workorder.move_subcontract_workorder_ids.picking_id.write({'state': 'cancel'}) + # 创建一个新的补货组 + procurement_group_id = self.env['procurement.group'].create({ + 'name': workorder.name, + 'partner_id': self.partner_id.id, + }) + move_dest_id = False + # 如果当前工单是是制造订单的最后一个工艺外协工单 + if workorder == next((workorder for workorder in reversed(sorted_workorders) if workorder.is_subcontract), + None): + move_dest_id = item.move_raw_ids[0].id else: - # 创建一个新的补货组 - procurement_group_id = self.env['procurement.group'].create({ - 'name': workorder.name, - 'partner_id': self.partner_id.id, - }) - move_dest_id = False - # 如果当前工单是是制造订单的最后一个工单 - if workorder == item.workorder_ids[-1]: - move_dest_id = item.move_raw_ids[0].id - else: - # 从sorted_workorders中找到上一工单的move - if sorted_workorders.index(workorder) > 0: - move_dest_id = sorted_workorders[sorted_workorders.index(workorder) - 1].move_subcontract_workorder_ids[1].id - new_picking = True - outcontract_picking_type_in = self.env.ref( - 'sf_manufacturing.outcontract_picking_in').id, - outcontract_picking_type_out = self.env.ref( - 'sf_manufacturing.outcontract_picking_out').id, - moves_in = self.env['stock.move'].sudo().create( - self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_in, procurement_group_id.id, move_dest_id)) - picking_in = self.create( - moves_in._get_new_picking_values_Res(item, workorder, 'WH/OCIN/')) - # pick_ids.append(picking_in.id) - moves_in.write( - {'picking_id': picking_in.id, 'state': 'waiting'}) - moves_in._assign_picking_post_process(new=new_picking) - moves_out = self.env['stock.move'].sudo().create( - self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_out, procurement_group_id.id, moves_in.id)) - workorder.write({'move_subcontract_workorder_ids': [(6, 0, [moves_in.id, moves_out.id])]}) - picking_out = self.create( - moves_out._get_new_picking_values_Res(item, workorder, 'WH/OCOUT/')) - # pick_ids.append(picking_out.id) - moves_out.write( - {'picking_id': picking_out.id, 'state': 'waiting'}) - moves_out._assign_picking_post_process(new=new_picking) - - + # 从sorted_workorders中找到上一工单的move + if len(sorted_workorders) > 1: + move_dest_id = \ + sorted_workorders[sorted_workorders.index(workorder) + 1].move_subcontract_workorder_ids[1].id + new_picking = True + outcontract_picking_type_in = self.env.ref( + 'sf_manufacturing.outcontract_picking_in').id, + outcontract_picking_type_out = self.env.ref( + 'sf_manufacturing.outcontract_picking_out').id, + context = dict(self.env.context) + context.update({ + 'default_production_id': item.id, # 添加额外信息到 context 中 + }) + moves_in = self.env['stock.move'].sudo().with_context(context).create( + self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_in, + procurement_group_id.id, move_dest_id)) + picking_in = self.create( + moves_in._get_new_picking_values_Res(item, workorder, 'WH/OCIN/')) + # pick_ids.append(picking_in.id) + moves_in.write( + {'picking_id': picking_in.id, 'state': 'waiting'}) + moves_in._assign_picking_post_process(new=new_picking) + # self.env.context.get('default_production_id') + moves_out = self.env['stock.move'].sudo().with_context(context).create( + self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_out, + procurement_group_id.id, moves_in.id)) + workorder.write({'move_subcontract_workorder_ids': [(6, 0, [moves_in.id, moves_out.id])]}) + picking_out = self.create( + moves_out._get_new_picking_values_Res(item, workorder, 'WH/OCOUT/')) + # pick_ids.append(picking_out.id) + moves_out.write( + {'picking_id': picking_out.id, 'state': 'waiting'}) + moves_out._assign_picking_post_process(new=new_picking) + @api.depends('move_type', 'immediate_transfer', 'move_ids.state', 'move_ids.picking_id') def _compute_state(self): super(StockPicking, self)._compute_state() @@ -706,7 +722,9 @@ class StockPicking(models.Model): if picking.move_ids: workorder = picking.move_ids[0].subcontract_workorder_id if picking.state == 'assigned': - if workorder.state in ['pending', 'waiting'] or workorder._get_surface_technics_purchase_ids().state in ['draft', 'sent']: + if workorder.state in ['pending', + 'waiting'] or workorder._get_surface_technics_purchase_ids().state in [ + 'draft', 'sent']: picking.state = 'waiting' @@ -716,10 +734,39 @@ class ReStockMove(models.Model): materiel_length = fields.Float(string='物料长度', digits=(16, 4)) materiel_width = fields.Float(string='物料宽度', digits=(16, 4)) materiel_height = fields.Float(string='物料高度', digits=(16, 4)) + part_number = fields.Char(string='零件图号', compute='_compute_part_info', store=True) + part_name = fields.Char(string='零件名称', compute='_compute_part_info', store=True) + + @api.depends('product_id') + def _compute_part_info(self): + for move in self: + if move.product_id.categ_id.type == '成品': + move.part_number = move.product_id.part_number + move.part_name = move.product_id.part_name + elif move.product_id.categ_id.type == '坯料': + if move.origin: + origin = move.origin.split(',')[0] if ',' in move.origin else move.origin + mrp_productio_info = self.env['mrp.production'].sudo().search( + [('name', '=', origin)]) + if mrp_productio_info: + move.part_number = mrp_productio_info.part_number + move.part_name = mrp_productio_info.part_name + else: + purchase_order_info = self.env['purchase.order'].sudo().search( + [('name', '=', origin)]) + if purchase_order_info: + mrp_production_ids = purchase_order_info._get_mrp_productions().ids + if mrp_production_ids: + mrp_productio_info = self.env['mrp.production'].sudo().search( + [('id', '=', mrp_production_ids[0])]) + if mrp_productio_info: + move.part_number = mrp_productio_info.part_number + move.part_name = mrp_productio_info.part_name def _get_stock_move_values_Res(self, item, picking_type_id, group_id, move_dest_ids=False): route_id = self.env.ref('sf_manufacturing.route_surface_technology_outsourcing').id - stock_rule = self.env['stock.rule'].sudo().search([('route_id', '=', route_id), ('picking_type_id', '=', picking_type_id)]) + stock_rule = self.env['stock.rule'].sudo().search( + [('route_id', '=', route_id), ('picking_type_id', '=', picking_type_id)]) move_values = { 'name': '推', 'company_id': item.company_id.id, @@ -731,6 +778,7 @@ class ReStockMove(models.Model): 'origin': item.name, 'group_id': group_id, 'move_dest_ids': [(6, 0, [move_dest_ids])] if move_dest_ids else False, + # 'production_id': item.id, # 'route_ids': False if not route else [(4, route.id)], 'date_deadline': datetime.now(), 'picking_type_id': picking_type_id, @@ -993,11 +1041,13 @@ class ReStockMove(models.Model): production = self.env['mrp.production'].search([('name', '=', self[0].origin)], limit=1, order='id asc') productions = self.env['mrp.production'].search( [('origin', '=', production.origin), ('product_id', '=', production.product_id.id)]) - res['origin'] = ','.join(productions.mapped('name')) + if productions.mapped('name'): + res['origin'] = ','.join(productions.mapped('name')) res['retrospect_ref'] = production.product_id.name return res - - subcontract_workorder_id = fields.Many2one('mrp.workorder', '外协工单组件', check_company=True, index='btree_not_null') + + subcontract_workorder_id = fields.Many2one('mrp.workorder', '外协工单组件', check_company=True, + index='btree_not_null') class ReStockQuant(models.Model): diff --git a/sf_manufacturing/security/ir.model.access.csv b/sf_manufacturing/security/ir.model.access.csv index 2880e662..b8862b8d 100644 --- a/sf_manufacturing/security/ir.model.access.csv +++ b/sf_manufacturing/security/ir.model.access.csv @@ -182,3 +182,8 @@ access_sf_manual_product_model_type_routing_sort_group_sf_mrp_user,sf_manual_pro access_sf_manual_product_model_type_routing_sort_manager,sf_manual_product_model_type_routing_sort,model_sf_manual_product_model_type_routing_sort,sf_base.group_sf_mrp_manager,1,1,1,1 access_sf_manual_product_model_type_routing_sort_group_plan_dispatch,sf_manual_product_model_type_routing_sort_group_plan_dispatch,model_sf_manual_product_model_type_routing_sort,sf_base.group_plan_dispatch,1,0,0,0 access_sf_detection_result_manager,sf_detection_result_manager,model_sf_detection_result,,1,1,1,1 + +access_mrp_workorder_batch_replan_wizard_group_plan_dispatch,mrp_workorder_batch_replan_wizard_group_plan_dispatch,model_mrp_workorder_batch_replan_wizard,sf_base.group_plan_dispatch,1,1,1,0 + +access_mrp_workorder_group_purchase_director,mrp_workorder,model_mrp_workorder,sf_base.group_purchase_director,1,1,0,0 +access_mrp_workorder_group_purchase,mrp_workorder,model_mrp_workorder,sf_base.group_purchase,1,1,0,0 \ No newline at end of file diff --git a/sf_manufacturing/static/src/js/agv_scheduling_cancel_confirm.js b/sf_manufacturing/static/src/js/agv_scheduling_cancel_confirm.js new file mode 100644 index 00000000..7fd19178 --- /dev/null +++ b/sf_manufacturing/static/src/js/agv_scheduling_cancel_confirm.js @@ -0,0 +1,50 @@ +odoo.define('sf_manufacturing.agv_scheduling_button_confirm', function (require) { + const core = require('web.core'); + const ajax = require('web.ajax'); + const Dialog = require('web.Dialog'); + var rpc = require('web.rpc'); + var _t = core._t; + + async function agv_scheduling_cancel_confirm(parent, {params}) { + const dialog = new Dialog(parent, { + title: "确认", + $content: $('

').append("工件正在配送中,确定取消"), + buttons: [ + { text: "确认", classes: 'btn-primary', close: true, click: () => dispatchConfirmed(parent, params) }, + { text: "取消", close: true }, + ], + }); + dialog.open(); + + + async function dispatchConfirmed(parent, params) { + rpc.query({ + model: 'sf.agv.scheduling', + method: 'button_cancel', + args: [params.agv_scheduling_id], + kwargs: { + context: params.context, + } + }).then(res => { + parent.services.action.doAction({ + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'target': 'new', + 'params': { + 'message': '取消成功!', + 'type': 'success', + 'sticky': false, + 'next': {'type': 'ir.actions.act_window_close'}, + } + }); + parent.services.action.doAction({ + 'type': 'ir.actions.client', + 'tag': 'reload', + }); + }) + } + } + + core.action_registry.add('agv_scheduling_cancel_confirm', agv_scheduling_cancel_confirm); + return agv_scheduling_cancel_confirm; +}); diff --git a/sf_manufacturing/static/src/js/agv_scheduling_resend_confirm.js b/sf_manufacturing/static/src/js/agv_scheduling_resend_confirm.js new file mode 100644 index 00000000..2ccda0e4 --- /dev/null +++ b/sf_manufacturing/static/src/js/agv_scheduling_resend_confirm.js @@ -0,0 +1,50 @@ +odoo.define('sf_manufacturing.agv_scheduling_resend_confirm', function (require) { + const core = require('web.core'); + const ajax = require('web.ajax'); + const Dialog = require('web.Dialog'); + var rpc = require('web.rpc'); + var _t = core._t; + + async function agv_scheduling_resend_confirm(parent, {params}) { + const dialog = new Dialog(parent, { + title: "确认", + $content: $('
').append("工件正在配送中,确定重新下发"), + buttons: [ + { text: "确认", classes: 'btn-primary', close: true, click: () => dispatchConfirmed(parent, params) }, + { text: "取消", close: true }, + ], + }); + dialog.open(); + + + async function dispatchConfirmed(parent, params) { + rpc.query({ + model: 'sf.agv.scheduling', + method: 'button_resend', + args: [params.agv_scheduling_id], + kwargs: { + context: params.context, + } + }).then(res => { + parent.services.action.doAction({ + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'target': 'new', + 'params': { + 'message': '重新下发成功!', + 'type': 'success', + 'sticky': false, + 'next': {'type': 'ir.actions.act_window_close'}, + } + }); + parent.services.action.doAction({ + 'type': 'ir.actions.client', + 'tag': 'reload', + }); + }) + } + } + + core.action_registry.add('agv_scheduling_resend_confirm', agv_scheduling_resend_confirm ); + return agv_scheduling_resend_confirm; +}); diff --git a/sf_manufacturing/static/src/js/workpiece_delivery_wizard_confirm.js b/sf_manufacturing/static/src/js/workpiece_delivery_wizard_confirm.js index d7900984..e99b4473 100644 --- a/sf_manufacturing/static/src/js/workpiece_delivery_wizard_confirm.js +++ b/sf_manufacturing/static/src/js/workpiece_delivery_wizard_confirm.js @@ -22,8 +22,7 @@ odoo.define('sf_manufacturing.action_dispatch_confirm', function (require) { rpc.query({ model: 'sf.workpiece.delivery.wizard', method: 'confirm', - args: [params.active_id] - , + args: [params.active_id], kwargs: { context: params.context, } diff --git a/sf_manufacturing/views/agv_scheduling_views.xml b/sf_manufacturing/views/agv_scheduling_views.xml index c24cdf94..d0d0bb85 100644 --- a/sf_manufacturing/views/agv_scheduling_views.xml +++ b/sf_manufacturing/views/agv_scheduling_views.xml @@ -33,6 +33,20 @@ class="btn-danger" confirm="你确定要取消这条记录吗?" /> + + + + + + + + + + + \ No newline at end of file diff --git a/sf_manufacturing/views/sale_order_views.xml b/sf_manufacturing/views/sale_order_views.xml index 1e5c4587..8e1d1061 100644 --- a/sf_manufacturing/views/sale_order_views.xml +++ b/sf_manufacturing/views/sale_order_views.xml @@ -13,11 +13,15 @@
-
+ + + + + + + sf.production.technology.wizard.form.view + sf.production.technology.wizard + +
+ + + +
+ 是否确认工艺调整 +
+
+
diff --git a/sf_manufacturing/wizard/rework_wizard.py b/sf_manufacturing/wizard/rework_wizard.py index eeae9ae9..148967be 100644 --- a/sf_manufacturing/wizard/rework_wizard.py +++ b/sf_manufacturing/wizard/rework_wizard.py @@ -62,6 +62,37 @@ class ReworkWizard(models.TransientModel): hidden_workorder_list.sort() item.hidden_workorder_ids = ','.join(hidden_workorder_list) + def efficacy_rework_wo(self, wk_ids): + """限制判断 """ + # 判断检测结果待处理所对应的工单是否勾选 + result_ids = self.production_id.detection_result_ids.filtered(lambda dr: dr.handle_result == '待处理') + work_id_list = [] + if result_ids: + work_id_list = [self.workorder_ids.filtered( + lambda wk: (wk.name == result_id.routing_type and wk.processing_panel == result_id.processing_panel + and wk.state == 'done')).id + for result_id in result_ids] + if len(wk_ids.filtered(lambda wk: wk.id in work_id_list)) != len(work_id_list): + raise ValidationError('存在【检测结果】为【待处理】所对应的工单未进行勾选!!!') + # 获取已完成的标准工单 + grouped_rw_ids = {key: list(group) for key, group in groupby(wk_ids, key=lambda w: w.processing_panel)} + for panel, panel_rw_ids in grouped_rw_ids.items(): + # 1、当制造订单内ZM面的工单都已完成时,返工勾选工序时只能勾选上ZM面的所有工序进行返工 + work_ids = self.workorder_ids.filtered(lambda w: w.state == 'done' and w.processing_panel == panel) + if len(work_ids) == 3 and len(panel_rw_ids) != 3: + raise ValidationError( + '因为[%s]面的工单已全部完成,如果要对[%s]面的工单进行返工,请勾选这个面的所有工单。' % (panel, panel)) + # 2、当FM工单在CNC工单进行选择返工,并将已全部完成的ZM面工序全部勾选上时,FM工单上所有的已完成的工单(装夹预调工单)也必须进行勾选 + if not wk_ids.filtered(lambda wk: wk.name == '装夹预调' and wk.processing_panel == panel): + if wk_ids.filtered(lambda wk: wk.name == 'CNC加工' and wk.processing_panel == panel): + sequence_max = wk_ids.filtered( + lambda wk: wk.name == 'CNC加工' and wk.processing_panel == panel).sequence + for wk_id in wk_ids.filtered(lambda wk: wk.sequence < sequence_max): + if len(wk_ids.filtered(lambda wk: wk.processing_panel == wk_id.processing_panel)) == 3: + raise ValidationError( + '由于在[%s]面之前存在整个面进行了勾选的情况,所以在勾选了[%s]面的【CNC加工】工单的时,请勾选[%s]面的装夹预调工单!' % ( + panel, panel, panel)) + def confirm(self): if self.routing_type in ['装夹预调', 'CNC加工']: self.is_clamp_measure = False @@ -79,36 +110,22 @@ class ReworkWizard(models.TransientModel): if self.hidden_workorder_ids: hidden_workorder_list = self.hidden_workorder_ids.split(',') rework_workorder_ids = self.workorder_ids.filtered(lambda w: str(w.id) in hidden_workorder_list) - # 限制判断 - # 1、当制造订单内ZM面的工单都已完成时,返工勾选工序时只能勾选上ZM面的所有工序进行返工 - # 2、当FM工单在CNC工单进行选择返工,并将已全部完成的ZM面工序全部勾选上时,FM工单上所有的已完成的工单(装夹预调工单)也必须进行勾选 - # 获取已完成的标准工单 - # done_normative_workorder_ids = self.workorder_ids.filtered( - # lambda w: w.state == 'done' and w.processing_panel is not False) - # # 获取需要返工的标准工单 - # rework_normative_workorder_ids = rework_workorder_ids.filtered( - # lambda w: w.processing_panel is not False) - # if rework_normative_workorder_ids: - # for rw in rework_normative_workorder_ids: - # if len(done_normative_workorder_ids.filtered( - # lambda w: w.processing_panel == rw.processing_panel)) == 3: - # pass + # 调用效验方法 + self.efficacy_rework_wo(rework_workorder_ids) else: raise ValidationError('请选择返工工单!!!') if rework_workorder_ids: - clamp_workorder_ids = None - if rework_workorder_ids: - # 限制 - # 1、单独返工CNC工单则不解绑托盘RFID,如单独返工装夹预调工单,则自动解绑托盘RFID; - # 2、返工CNC工单和装夹预调工单则自动解绑RFID - clamp_workorder_ids = rework_workorder_ids.filtered(lambda rp: rp.routing_type == '装夹预调') - if clamp_workorder_ids: - for clamp_workorder_id in clamp_workorder_ids: - self.production_id.workorder_ids.filtered( - lambda wk: wk.processing_panel == clamp_workorder_id.processing_panel).write( - {'rfid_code': False}) - # 返工工单状态设置为【返工】 - rework_workorder_ids.write({'state': 'rework'}) + # 限制 + # 1、单独返工CNC工单则不解绑托盘RFID,如单独返工装夹预调工单,则自动解绑托盘RFID; + # 2、返工CNC工单和装夹预调工单则自动解绑RFID + clamp_workorder_ids = rework_workorder_ids.filtered(lambda rp: rp.routing_type == '装夹预调') + if clamp_workorder_ids: + for clamp_workorder_id in clamp_workorder_ids: + self.production_id.workorder_ids.filtered( + lambda wk: wk.processing_panel == clamp_workorder_id.processing_panel).write( + {'rfid_code': None}) + # 返工工单状态设置为【返工】 + rework_workorder_ids.write({'state': 'rework'}) # 查询返工工单对应的工艺设计记录,并调用方法拼接数据,用于创建新的工单 workorders_values = [] for work in rework_workorder_ids: @@ -119,7 +136,7 @@ class ReworkWizard(models.TransientModel): and item.panel == work.processing_panel)) if route: work_list = self.env['mrp.workorder'].json_workorder_str(self.production_id, route[0]) - work_list[2].update({'tag_type': '重新加工'}) + work_list[2].update({'tag_type': '重新加工', 'sequence': 0}) workorders_values.append(work_list) # 创建新工单,并进行返工配置的相关操作 if workorders_values: @@ -129,6 +146,11 @@ class ReworkWizard(models.TransientModel): new_pre_workorder_ids = self.production_id.workorder_ids.filtered( lambda kw: kw.routing_type == '装夹预调' and kw.sequence == 0) self.production_id._reset_work_order_sequence() + # ====新工单绑定rfid=== + for new_work_id in new_work_ids: + if new_work_id.routing_type in ['CNC加工', '解除装夹']: + new_work_id.write({'rfid_code': self.production_id.workorder_ids.filtered( + lambda wk: wk.sequence == new_work_id.sequence - 1).rfid_code}) self.production_id.detection_result_ids.filtered( lambda ap1: ap1.handle_result == '待处理').write({'handle_result': '已处理'}) panels = [] # 返工的加工面 @@ -249,11 +271,7 @@ class ReworkWizard(models.TransientModel): elif self.programming_state in ['待编程', '编程中']: self.production_id.write( {'programming_state': '编程中', 'work_state': '编程中', 'is_rework': True}) - # ==================申请重新编程======================= - if self.is_reprogramming is True: - self.production_id.update_programming_state() - self.production_id.write( - {'programming_state': '编程中', 'work_state': '编程中'}) + # ================================================= if self.production_id.state == 'progress': self.production_id.write({'programming_state': '已编程', 'work_state': '已编程'}) if self.reprogramming_num >= 1 and self.programming_state == '已编程': @@ -264,6 +282,13 @@ class ReworkWizard(models.TransientModel): productions_not_delivered.write( {'state': 'progress', 'programming_state': '已编程', 'work_state': '已编程', 'is_rework': False}) + # ==================申请重新编程======================= + if self.is_reprogramming is True: + self.production_id.update_programming_state() + self.production_id.write( + {'programming_state': '编程中', 'work_state': '编程中', 'state': 'progress'}) + # ================= 返工完成,制造订单状态置为加工中 ============== + self.production_id.write({'state': 'progress', 'is_rework': False}) @api.onchange('production_id') def onchange_processing_panel_id(self): diff --git a/sf_manufacturing/wizard/rework_wizard_views.xml b/sf_manufacturing/wizard/rework_wizard_views.xml index 05d1cc04..aad8a692 100644 --- a/sf_manufacturing/wizard/rework_wizard_views.xml +++ b/sf_manufacturing/wizard/rework_wizard_views.xml @@ -14,7 +14,7 @@ - + sale.order + + 待确认供货方式 + sale.order + + + + 待确认加工路线 + mrp.production + 调拨入库 @@ -47,7 +56,7 @@ 工单已下发通知 - mrp.workorder + product.product @@ -106,5 +115,45 @@ 设备故障 sf.maintenance.logs + + + 待排程 + mrp.production + + + + 委外加工采购单提醒 + purchase.order + + + + 外购订单采购单提醒 + purchase.order + + + + 工序外协采购单通知 + purchase.order + + + + 工序外协发料通知 + mrp.production + + + + 采购订单预警提醒 + purchase.order + + + + 采购单已逾期提醒 + purchase.order + + + + 待质检提醒 + product.product + \ No newline at end of file diff --git a/sf_message/data/template_data.xml b/sf_message/data/template_data.xml index beb37771..11f05a3c 100644 --- a/sf_message/data/template_data.xml +++ b/sf_message/data/template_data.xml @@ -10,7 +10,7 @@ markdown normal ### 待接单提醒: -单号:销售订单[{{name}}]({{url}}) +单号:询价单[{{name}}]({{url}}) 事项:请确认是否接单。 @@ -72,14 +72,14 @@ markdown normal ### 坯料发料提醒: -单号:产品[{{product_id}}]({{request_url}}) -事项:共{{number}}个生产发料单待确认处理 +单号:发料单[{{name}}]({{request_url}}) +事项:请确认坯料发料单并处理 工单已下发通知 - - mrp.workorder + + product.product markdown normal @@ -275,5 +275,131 @@ 机台号:[{{maintenance_equipment_id.name}}]({{url}}) 事项:{{create_date}}故障报警 + + + 待确认供货方式 + + sale.order + + markdown + normal + ### 待确认供货方式提醒: +单号:销售订单[{{name}}]({{url}}) +事项:请确认供货方式。 + + + + + 待确认加工路线 + + mrp.production + + markdown + normal + ### 待确认加工路线提醒: +单号:产品[{{name}}]({{url}}) +事项:有{{production_num}}个制造订单需要确认加工路线。 + + + + + 待排程 + + mrp.production + + markdown + normal + ### 待排程提醒: +单号:产品[{{name}}]({{url}}) +事项:有{{production_num}}个制造订单待排程。 + + + + + 委外加工采购单提醒 + + purchase.order + + markdown + normal + ### 委外加工采购通知: +单号:委外加工采购单[{{name}}]({{request_url}}) +事项:请确认委外采购单并处理。 + + + + 外购订单采购单提醒 + + purchase.order + + markdown + normal + ### 外购订单采购通知: +单号:外购采购单[{{name}}]({{request_url}}) +事项:请确认外购采购单并处理。 + + + + 工序外协采购单通知 + + purchase.order + + markdown + normal + ### 工序外协采购单通知: +单号:工序外协采购,产品[{{name}}]({{url}}) +事项:请确认{{num}}个工序外协采购单并处理。 + + + + 工序外协发料通知 + + mrp.production + + markdown + normal + ### 工序外协发料提醒: +单号:产品[{{name}}]({{url}})发料单 +事项:请确认{{num}}个工序外协发料单并发料处理。 + + + + + 采购订单预警提醒 + + purchase.order + + markdown + timing + normal + ### 采购订单预警提醒 +事项:[共有{{num}}个采购订单有逾期风险]({{url}}) + + + + + 采购单已逾期提醒 + + purchase.order + + markdown + timing + normal + ### 采购单已逾期提醒 +事项:[共有{{num}}个采购订单已逾期]({{url}}) + + + + + 待质检提醒 + + product.product + + markdown + normal + ### 待质检提醒: +单号:产品[{{name}}]({{url}}) +事项:有{{num}}个质检单需要处理。 + \ No newline at end of file diff --git a/sf_message/models/__init__.py b/sf_message/models/__init__.py index 0b9c9ad6..18c6b873 100644 --- a/sf_message/models/__init__.py +++ b/sf_message/models/__init__.py @@ -10,3 +10,7 @@ from . import sf_message_functional_tool_dismantle from . import sf_message_mrp_production from . import sf_message_quality_cnc_test from . import sf_message_maintenance_logs +from . import sf_message_mrp_production_wizard +from . import sf_message_mrp_production_adjust_wizard +from . import sf_message_product +from . import sf_message_quality_check diff --git a/sf_message/models/sf_message_maintenance_logs.py b/sf_message/models/sf_message_maintenance_logs.py index 94a4c5c5..ab15864f 100644 --- a/sf_message/models/sf_message_maintenance_logs.py +++ b/sf_message/models/sf_message_maintenance_logs.py @@ -13,11 +13,11 @@ class SFMessageMaintenanceLogs(models.Model): return res def _get_message(self, message_queue_ids): - contents = super(SFMessageMaintenanceLogs, self)._get_message(message_queue_ids) + contents, _ = super(SFMessageMaintenanceLogs, self)._get_message(message_queue_ids) url = self.env['ir.config_parameter'].get_param('web.base.url') action_id = self.env.ref('sf_maintenance.action_maintenance_logs').id for index, content in enumerate(contents): maintenance_logs_id = self.env['sf.maintenance.logs'].browse(message_queue_ids[index].res_id) url = url + '/web#id=%s&view_type=form&action=%s' % (maintenance_logs_id.id, action_id) contents[index] = content.replace('{{url}}', url) - return contents \ No newline at end of file + return contents, message_queue_ids \ No newline at end of file diff --git a/sf_message/models/sf_message_mrp_production.py b/sf_message/models/sf_message_mrp_production.py index a60f2161..9fd21e9e 100644 --- a/sf_message/models/sf_message_mrp_production.py +++ b/sf_message/models/sf_message_mrp_production.py @@ -14,19 +14,24 @@ class SFMessageMrpProduction(models.Model): 'workorder_ids.state', 'product_qty', 'qty_producing') def _compute_state(self): super(SFMessageMrpProduction, self)._compute_state() - for record in self: - if record.state in ['scrap', 'done']: - # 查询制造订单下的所有未完成的生产订单 - mrp_production = record.env['mrp.production'].search( - [('origin', '=', record.origin), ('state', 'not in', ['scrap', 'done'])]) - if not mrp_production: - mrp_production_queue = self.env["jikimo.message.queue"].search([('res_id', '=', record.id)]) - if not mrp_production_queue: - record.add_queue('生产完工入库提醒') + try: + for record in self: + if record.state in ['scrap', 'done']: + # 查询制造订单下的所有未完成的生产订单 + mrp_production = record.env['mrp.production'].search( + [('origin', '=', record.origin), ('state', 'not in', ['scrap', 'done'])]) + if not mrp_production: + mrp_production_queue = self.env["jikimo.message.queue"].search([('res_id', '=', record.id)]) + if not mrp_production_queue: + record.add_queue('生产完工入库提醒') + except Exception as e: + logging.info('add_queue生产完工入库提醒 error:%s' % e) # 获取发送消息内容 def _get_message(self, message_queue_ids): contents = [] + unique_products = set() + technology_to_confirmed = set() for message_queue_id in message_queue_ids: if message_queue_id.message_template_id.name == '生产完工入库提醒': content = message_queue_id.message_template_id.content @@ -40,7 +45,58 @@ class SFMessageMrpProduction(models.Model): content = content.replace('{{name}}', stock_picking_sfp.name).replace( '{{sale_order_name}}', mrp_production.origin).replace('{{request_url}}', url) contents.append(content) + if message_queue_id.message_template_id.name == '待确认加工路线': + content = message_queue_id.message_template_id.content + mrp_production = self.env['mrp.production'].sudo().search([('id', '=', int(message_queue_id.res_id))]) + technology_to_confirmed.add(mrp_production.product_id.id) + if message_queue_id.message_template_id.name == '待排程': + content = message_queue_id.message_template_id.content + mrp_production = self.env['mrp.production'].sudo().search([('id', '=', int(message_queue_id.res_id))]) + unique_products.add(mrp_production.product_id.id) + if message_queue_id.message_template_id.name == '工序外协发料通知': + content = message_queue_id.message_template_id.content + mrp_production = self.env['mrp.production'].sudo().search([('id', '=', int(message_queue_id.res_id))]) + mrp_production_list = self.env['mrp.production'].sudo().search( + [('product_id', '=', mrp_production.product_id.id)]) + mrp_production_names = mrp_production_list.mapped('name') + stock_picking_num = self.env['stock.picking'].sudo().search_count( + [('origin', 'in', mrp_production_names), ('state', '=', 'assigned')]) + if stock_picking_num >= 1: + url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + action_id = self.env.ref('sf_message.action_picking_outsourced_tree_ready').id + menu_id = self.env.ref('stock.menu_stock_root').id + url_with_id = f"{url}/web#view_type=list&action={action_id}&menu_id={menu_id}" + content = content.replace('{{name}}', mrp_production.product_id.name).replace('{{url}}', + url_with_id).replace( + '{{num}}', str(stock_picking_num)) + contents.append(content) + if unique_products: + action_id = self.env.ref('sf_plan.sf_production_plan_action1').id + unique_products_contents = self.get_production_info(content, unique_products, 'confirmed', + action_id) + contents.extend(unique_products_contents) + if technology_to_confirmed: + action_id = self.env.ref('mrp.mrp_production_action').id + technology_to_confirmed_contents = self.get_production_info(content, technology_to_confirmed, + 'technology_to_confirmed', + action_id) + contents.extend(technology_to_confirmed_contents) logging.info('生产完工入库提醒: %s' % contents) + return contents, message_queue_ids + + def get_production_info(self, content, product_ids, state, action_id): + contents = [] + for products_id in product_ids: + product_name = self.env['product.product'].sudo().search([('id', '=', products_id)]).name + production_num = self.env['mrp.production'].sudo().search_count( + [('product_id', '=', products_id), ('state', '=', state)]) + if production_num >= 1: + url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + url_with_id = f"{url}/web#view_type=list&action={action_id}" + new_content = (content.replace('{{name}}', product_name) + .replace('{{url}}', url_with_id) + .replace('{{production_num}}', str(production_num))) + contents.append(new_content) return contents def request_url(self, id): @@ -48,7 +104,7 @@ class SFMessageMrpProduction(models.Model): action_id = self.env.ref('stock.action_picking_tree_all').id menu_id = self.env['ir.model.data'].search([('name', '=', 'module_theme_treehouse')]).id # 查询参数 - params = {'id': id, 'menu_id': menu_id, 'action': action_id, 'model': 'mrp.production', + params = {'id': id, 'menu_id': menu_id, 'action': action_id, 'model': 'mrp.production', 'view_type': 'form'} # 拼接查询参数 query_string = urlencode(params) diff --git a/sf_message/models/sf_message_mrp_production_adjust_wizard.py b/sf_message/models/sf_message_mrp_production_adjust_wizard.py new file mode 100644 index 00000000..224b845f --- /dev/null +++ b/sf_message/models/sf_message_mrp_production_adjust_wizard.py @@ -0,0 +1,17 @@ +import logging +from odoo import models, fields, api, _ + + +class SFMessageMrpProductionAdjustWizard(models.TransientModel): + _name = 'sf.production.technology.re_adjust.wizard' + _description = "制造订单工艺调整" + _inherit = ['sf.production.technology.re_adjust.wizard', 'jikimo.message.dispatch'] + + def confirm(self): + super(SFMessageMrpProductionAdjustWizard, self).confirm() + try: + for production_info in self.production_id: + if production_info.state == 'technology_to_confirmed': + production_info.add_queue('待确认加工路线') + except Exception as e: + logging.info('add_queue待确认加工路线 error:%s' % e) diff --git a/sf_message/models/sf_message_mrp_production_wizard.py b/sf_message/models/sf_message_mrp_production_wizard.py new file mode 100644 index 00000000..990bdd49 --- /dev/null +++ b/sf_message/models/sf_message_mrp_production_wizard.py @@ -0,0 +1,23 @@ +import logging +from odoo import models, fields, api, _ + + +class SFMessageMrpProductionWizard(models.TransientModel): + _name = 'sf.production.technology.wizard' + _description = "制造订单工艺确认向导" + _inherit = ['sf.production.technology.wizard', 'jikimo.message.dispatch'] + + def confirm(self): + productions = super(SFMessageMrpProductionWizard, self).confirm() + try: + for production_info in self.production_id: + if production_info.state == 'confirmed' and production_info.product_id.categ_id.type == '成品': + production_info.add_queue('待排程') + for production_id in productions: + workorder_ids = production_id.workorder_ids.filtered( + lambda p: p.routing_type == '表面工艺' and p.state != 'cancel') + for workorder_id in workorder_ids: + purchase_orders_id = workorder_id._get_surface_technics_purchase_ids() + purchase_orders_id.add_queue('工序外协采购单通知') + except Exception as e: + logging.info('add_queue待排程 error:%s' % e) diff --git a/sf_message/models/sf_message_product.py b/sf_message/models/sf_message_product.py new file mode 100644 index 00000000..0f2cfd7e --- /dev/null +++ b/sf_message/models/sf_message_product.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +from odoo import models, fields, api, _ +from urllib.parse import urlencode + + +class SFMessageProduct(models.Model): + _name = 'product.product' + _inherit = ['product.product', 'jikimo.message.dispatch'] + + def _get_message(self, message_queue_ids): + contents = [] + for message_queue_id in message_queue_ids: + if message_queue_id.message_template_id.name == '工单已下发通知': + content = message_queue_id.message_template_id.content + product_product = self.env['product.product'].sudo().search([('id', '=', int(message_queue_id.res_id))]) + mrp_production_list = self.env['mrp.production'].sudo().search( + [('product_id', '=', product_product.id)]) + production_num = 0 + for mrp_production_info in mrp_production_list: + routing_type = '人工线下加工' if mrp_production_info.production_type == '人工线下加工' else '装夹预调' + mrp_production_ready = mrp_production_info.workorder_ids.filtered( + lambda w: w.routing_type == routing_type and w.state == 'ready') + if mrp_production_ready: + production_num += 1 + if production_num >= 1: + url = self.get_request_url() + content = content.replace('{{product_id}}', product_product.name).replace( + '{{number}}', str(production_num)).replace( + '{{request_url}}', url) + contents.append(content) + if message_queue_id.message_template_id.name == '待质检提醒': + content = message_queue_id.message_template_id.content + product_product = self.env['product.product'].sudo().search([('id', '=', int(message_queue_id.res_id))]) + quality_check_num = self.env['quality.check'].sudo().search_count( + [('product_id', '=', product_product.id), ('quality_state', '=', 'none')]) + if quality_check_num >= 1: + url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + action_id = self.env.ref('quality_control.quality_check_action_report').id + url_with_id = f"{url}/web#view_type=list&action={action_id}" + content = content.replace('{{name}}', product_product.name).replace('{{url}}', url_with_id).replace( + '{{num}}', str(quality_check_num)) + contents.append(content) + return contents, message_queue_ids + + def get_request_url(self): + url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + action_id = self.env.ref('sf_message.mrp_workorder_issued_action').id + menu_id = self.env.ref('mrp.menu_mrp_root').id + active_id = self.env['mrp.workcenter'].sudo().search([('name', '=', '工件装夹中心')]).id + # 查询参数 + params = {'menu_id': menu_id, 'action': action_id, 'model': 'mrp.workorder', + 'view_type': 'list', 'active_id': active_id} + # 拼接查询参数 + query_string = urlencode(params) + # 拼接URL + full_url = url + "/web#" + query_string + return full_url diff --git a/sf_message/models/sf_message_purchase.py b/sf_message/models/sf_message_purchase.py index e3fe269f..59b8a36a 100644 --- a/sf_message/models/sf_message_purchase.py +++ b/sf_message/models/sf_message_purchase.py @@ -8,20 +8,62 @@ class SFMessagePurchase(models.Model): def _get_message(self, message_queue_ids): contents = [] + process_outsourcing = set() for message_queue_id in message_queue_ids: - if message_queue_id.message_template_id.name == '坯料采购提醒': + if message_queue_id.message_template_id.name in ( + '坯料采购提醒', '委外加工采购单提醒', '外购订单采购单提醒'): content = message_queue_id.message_template_id.content url = self.request_url(int(message_queue_id.res_id)) purchase_order_line = self.env['purchase.order'].search([('id', '=', int(message_queue_id.res_id))]) content = content.replace('{{name}}', purchase_order_line.name).replace( '{{request_url}}', url) contents.append(content) - return contents + if message_queue_id.message_template_id.name == '工序外协采购单通知': + content = message_queue_id.message_template_id.content + purchase_order_line = self.env['purchase.order'].sudo().search( + [('id', '=', int(message_queue_id.res_id))]) + mrp_production = self.env['mrp.production'].sudo().search([('name', '=', purchase_order_line.origin)]) + process_outsourcing.add(mrp_production.product_id.id) + if message_queue_id.message_template_id.name in ('采购订单预警提醒', '采购单已逾期提醒'): + content = message_queue_id.message_template_id.content + if message_queue_id.message_template_id.name == '采购订单预警提醒': + domain = [('delivery_warning', '=', 'warning')] + action_id = self.env.ref("sf_message.purchase_form_warning_action").id + else: + domain = [('delivery_warning', '=', 'overdue')] + action_id = self.env.ref("sf_message.purchase_form_overdue_action").id + purchase_order_num = self.env['purchase.order'].sudo().search_count(domain) + if purchase_order_num >= 1: + url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + menu_id = self.env.ref('purchase.menu_purchase_form_action').id + url_with_id = f"{url}/web#view_type=list&action={action_id}&menu_id={menu_id}" + content = content.replace('{{url}}', url_with_id).replace('{{num}}', str(purchase_order_num)) + contents.append(content) + if process_outsourcing: + content_info = content + for products_id in process_outsourcing: + production_num = 0 + product_name = self.env['product.product'].sudo().search([('id', '=', products_id)]).name + production_list = self.env['mrp.production'].sudo().search( + [('product_id', '=', products_id), ('state', '=', 'confirmed')]) + for production_info in production_list: + workorder_ids = len(production_info.workorder_ids.filtered( + lambda p: p.routing_type == '表面工艺' and p.state != 'cancel')) + production_num += workorder_ids + if production_num >= 1: + url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + action_id = self.env.ref('purchase.purchase_form_action').id + url_with_id = f"{url}/web#view_type=list&action={action_id}" + new_content = (content_info.replace('{{name}}', product_name) + .replace('{{url}}', url_with_id) + .replace('{{num}}', str(production_num))) + contents.append(new_content) + return contents, message_queue_ids def request_url(self, id): url = self.env['ir.config_parameter'].get_param('web.base.url') action_id = self.env.ref('purchase.purchase_form_action').id - menu_id = self.env['ir.model.data'].search([('name', '=', 'module_website_payment')]).id + menu_id = self.env.ref('purchase.menu_purchase_form_action').id # 查询参数 params = {'id': id, 'menu_id': menu_id, 'action': action_id, 'model': 'purchase.order', @@ -31,3 +73,27 @@ class SFMessagePurchase(models.Model): # 拼接URL full_url = url + "/web#" + query_string return full_url + + def _overdue_or_warning_func(self): + last_overdue_order, last_warning_order = super(SFMessagePurchase, self)._overdue_or_warning_func() + if last_overdue_order: + business_node_id = self.env.ref('sf_message.template_purchase_order_overdue').id + purchase_order_has = self._get_message_queue(business_node_id) + if not purchase_order_has: + last_overdue_order.add_queue("采购单已逾期提醒") + if last_warning_order: + business_node_id = self.env.ref('sf_message.template_purchase_order_warning').id + purchase_order_has = self._get_message_queue(business_node_id) + if not purchase_order_has: + last_warning_order.add_queue("采购订单预警提醒") + + def _get_message_queue(self, business_node_id): + message_template = self.env["jikimo.message.template"].sudo().search([ + ("model", "=", self._name), + ("bussiness_node_id", "=", business_node_id) + ], limit=1) + purchase_order_has = self.env['jikimo.message.queue'].sudo().search([ + ('message_status', '=', 'pending'), + ('message_template_id', '=', message_template.id) + ]) + return purchase_order_has diff --git a/sf_message/models/sf_message_quality_check.py b/sf_message/models/sf_message_quality_check.py new file mode 100644 index 00000000..15713791 --- /dev/null +++ b/sf_message/models/sf_message_quality_check.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- +import logging +from odoo import models, fields, api, _ + + +class SFMessageQualityCheck(models.Model): + _name = 'quality.check' + _inherit = ['quality.check', 'jikimo.message.dispatch'] + + @api.model_create_multi + def create(self, vals_list): + result = super().create(vals_list) + try: + # 判断是否为web页面创建请求 + is_web_request = self.env.context.get('is_web_request', False) + if not is_web_request: + for obj in result: + jikimo_message_queue = self.get_message_queue(obj.product_id.id) + if not jikimo_message_queue: + obj.product_id.add_queue('待质检提醒') + except Exception as e: + logging.info('add_queue待质检提醒 error:%s' % e) + return result + + def get_message_queue(self, res_id): + business_node_id = self.env.ref('sf_message.bussiness_quality_check').id + message_template = self.env["jikimo.message.template"].sudo().search([ + ("name", "=", '待质检提醒'), + ("bussiness_node_id", "=", business_node_id) + ], limit=1) + jikimo_message_queue = self.env['jikimo.message.queue'].sudo().search( + [('res_id', '=', res_id), ("message_status", "in", ("pending", "sent")), + ('message_template_id', '=', message_template.id)]) + return jikimo_message_queue diff --git a/sf_message/models/sf_message_quality_cnc_test.py b/sf_message/models/sf_message_quality_cnc_test.py index 0ddc1391..43969682 100644 --- a/sf_message/models/sf_message_quality_cnc_test.py +++ b/sf_message/models/sf_message_quality_cnc_test.py @@ -33,4 +33,4 @@ class SFMessageQualityCncTest(models.Model): content_template = content.replace('{{judge_num}}', str(i)) content_template = content_template.replace('{{url}}', url_with_id) contents.append(content_template) - return contents + return contents, message_queue_ids diff --git a/sf_message/models/sf_message_sale.py b/sf_message/models/sf_message_sale.py index 58d1a021..f4712f67 100644 --- a/sf_message/models/sf_message_sale.py +++ b/sf_message/models/sf_message_sale.py @@ -8,17 +8,6 @@ class SFMessageSale(models.Model): _name = 'sale.order' _inherit = ['sale.order', 'jikimo.message.dispatch'] - @api.model_create_multi - def create(self, vals_list): - res = super(SFMessageSale, self).create(vals_list) - if res: - try: - logging.info('add_queue res:%s' % res) - res.add_queue('待接单') - except Exception as e: - logging.info('add_queue error:%s' % e) - return res - # 确认接单 def action_confirm(self): res = super(SFMessageSale, self).action_confirm() @@ -29,19 +18,34 @@ class SFMessageSale(models.Model): purchase_order_id = [] if picking_ids: for picking_id in picking_ids: + picking_id.add_queue('待确认加工路线') purchase_order_ids = ( picking_id.procurement_group_id.stock_move_ids.created_purchase_line_id.order_id | picking_id.procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id).ids purchase_order_id.extend(purchase_order_ids) if purchase_order_id: - purchase_order_list = self.env['purchase.order'].sudo().search([('id', 'in', purchase_order_id)]) + purchase_order_list = self.env['purchase.order'].sudo().search( + [('id', 'in', purchase_order_id)]) for purchase_order_info in purchase_order_list: purchase_order_info.add_queue('坯料采购提醒') + purchase_order_ids = self.order_line.purchase_line_ids.order_id | self.procurement_group_id.stock_move_ids.created_purchase_line_id.order_id | self.procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id + for purchase_order_id in purchase_order_ids: + if purchase_order_id.purchase_type == 'outsourcing': + purchase_order_id.add_queue('委外加工采购单提醒') + if purchase_order_id.purchase_type == 'outside': + purchase_order_id.add_queue('外购订单采购单提醒') except Exception as e: logging.info('add_queue error:%s' % e) logging.info('action_confirm res:%s' % res) return res + def confirm_to_supply_method(self): + super(SFMessageSale, self).confirm_to_supply_method() + try: + self.add_queue('待确认供货方式') + except Exception as e: + logging.info('add_queue待确认供货方式 error:%s' % e) + # 继承并重写jikimo.message.dispatch的_get_message() def _get_message(self, message_queue_ids): contents = [] @@ -53,14 +57,16 @@ class SFMessageSale(models.Model): time_range = timedelta(minutes=2) i = 0 for item in message_queue_ids: - if item.message_template_id.bussiness_node_id.name == '待接单': - content = super(SFMessageSale, self)._get_message(item) - action_id = self.env.ref('sale.action_quotations_with_onboarding').id + if item.message_template_id.bussiness_node_id.name in ('待接单', '待确认供货方式'): + content, _ = super(SFMessageSale, self)._get_message(item) + action_id = self.env.ref('sale.action_quotations_with_onboarding').id \ + if item.message_template_id.bussiness_node_id.name == '待接单' \ + else self.env.ref('sale.action_orders').id url_with_id = f"{url}/web#id={item.res_id}&view_type=form&action={action_id}" content = content[0].replace('{{url}}', url_with_id) contents.append(content) elif item.message_template_id.bussiness_node_id.name == '确认接单': - content = super(SFMessageSale, self)._get_message(item) + content, _ = super(SFMessageSale, self)._get_message(item) sale_order_line = self.env['sale.order.line'].sudo().search([('order_id', '=', int(item.res_id))]) product = sale_order_line[0].product_id.name if len(sale_order_line) == 1 else '%s...' % \ sale_order_line[ @@ -97,7 +103,7 @@ class SFMessageSale(models.Model): elif bussiness_node == '销售订单已逾期': content = content_template.replace('{{overdue_num}}', str(i)) contents.append(content) - return contents + return contents, message_queue_ids # # 销售订单逾期预警和已逾期 def _overdue_or_warning_func(self): diff --git a/sf_message/models/sf_message_stock_picking.py b/sf_message/models/sf_message_stock_picking.py index 6e80c670..a2c17407 100644 --- a/sf_message/models/sf_message_stock_picking.py +++ b/sf_message/models/sf_message_stock_picking.py @@ -12,26 +12,44 @@ class SFMessageStockPicking(models.Model): @api.model_create_multi def create(self, vals): result = super(SFMessageStockPicking, self).create(vals) - for obj in result: - if obj.location_id.name == '进货' and obj.location_dest_id.name == '刀具房': - obj.add_queue('调拨入库') + try: + for obj in result: + if obj.location_id.name == '进货' and obj.location_dest_id.name == '刀具房': + obj.add_queue('调拨入库') + except Exception as e: + logging.info('add_queue调拨入库 error:%s' % e) return result @api.depends('move_type', 'immediate_transfer', 'move_ids.state', 'move_ids.picking_id') def _compute_state(self): super(SFMessageStockPicking, self)._compute_state() - for record in self: - if record.state == 'assigned' and record.check_in == 'PC': - record.add_queue('坯料发料提醒') + try: + for record in self: + if (record.state == 'assigned' and record.picking_type_id.sequence_code == 'PC' + and record.product_id.categ_id.type == '坯料'): + record.add_queue('坯料发料提醒') - if record.picking_type_id.sequence_code == 'SFP' and record.state == 'done': - stock_picking_sfp = record.env['stock.picking'].search( - [('origin', '=', record.origin), ('state', '!=', 'done'), - ('picking_type_id.sequence_code', '=', 'SFP')]) - if not stock_picking_sfp: - stock_picking_send = self.env["jikimo.message.queue"].sudo().search([('res_id', '=', record.id)]) - if not stock_picking_send: - record.add_queue('订单发货提醒') + if record.picking_type_id.sequence_code == 'SFP' and record.state == 'done': + stock_picking_sfp = record.env['stock.picking'].search( + [('origin', '=', record.origin), ('state', '!=', 'done'), + ('picking_type_id.sequence_code', '=', 'SFP')]) + if not stock_picking_sfp: + stock_picking_send = self.env["jikimo.message.queue"].sudo().search( + [('res_id', '=', record.id)]) + if not stock_picking_send: + record.add_queue('订单发货提醒') + if record.picking_type_id.sequence_code == 'OCOUT' and record.state == 'assigned': + mrp_production = self.env['mrp.production'].sudo().search([('name', '=', record.origin)]) + production_list = self.env['mrp.production'].sudo().search( + [('product_id', '=', mrp_production.product_id.id)]) + manufacturing_order_names = production_list.mapped('name') + stock_picking_list = self.env['stock.picking'].sudo().search( + [('origin', 'in', manufacturing_order_names), ('picking_type_id.sequence_code', '=', 'OCOUT')]) + all_ready_or_done = all(picking.state in ['assigned', 'done'] for picking in stock_picking_list) + if all_ready_or_done: + mrp_production.add_queue('工序外协发料通知') + except Exception as e: + logging.info('add_queue_compute_state error:%s' % e) def deal_stock_picking_sfp(self, message_queue_id): # 处理订单发货提醒 content = None @@ -49,32 +67,23 @@ class SFMessageStockPicking(models.Model): def _get_message(self, message_queue_ids): contents = [] - product_id = [] for message_queue_id in message_queue_ids: - i = 0 if message_queue_id.message_template_id.name == '坯料发料提醒': content = message_queue_id.message_template_id.content - stock_picking_line = self.env['stock.picking'].sudo().search([('id', '=', int(message_queue_id.res_id))]) - mrp_production_info = self.env['mrp.production'].sudo().search( - [('name', '=', stock_picking_line.origin)]) - mrp_production_list = self.env['mrp.production'].sudo().search( - [('product_id', '=', mrp_production_info.product_id.id)]) - for mrp_production_line in mrp_production_list: - picking_ids = mrp_production_line.picking_ids - for picking_id in picking_ids: - if picking_id.state == 'assigned' and picking_id.check_in == 'PC': - i += 1 - if i > 0 and mrp_production_info.product_id.id not in product_id: - url = self.request_url() - content = content.replace('{{product_id}}', mrp_production_info.product_id.name).replace( - '{{number}}', str(i)).replace('{{request_url}}', url) - product_id.append(mrp_production_info.product_id.id) - contents.append(content) + stock_picking_line = self.env['stock.picking'].sudo().search( + [('id', '=', int(message_queue_id.res_id))]) + url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + action_id = self.env.ref('stock.action_picking_tree_ready').id + menu_id = self.env.ref('stock.menu_stock_root').id + url_with_id = f"{url}/web#view_type=form&action={action_id}&menu_id={menu_id}&id={stock_picking_line.id}" + content = content.replace('{{name}}', stock_picking_line.name).replace( + '{{request_url}}', url_with_id) + contents.append(content) elif message_queue_id.message_template_id.name == '订单发货提醒': content = self.deal_stock_picking_sfp(message_queue_id) if content: contents.append(content) - return contents + return contents, message_queue_ids def get_special_url(self, id, tmplate_name, special_name, model_id): menu_id = 0 @@ -86,19 +95,6 @@ class SFMessageStockPicking(models.Model): else: return super(SFMessageStockPicking, self).get_special_url(id, tmplate_name, special_name, model_id) - def request_url(self): - url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') - action_id = self.env.ref('stock.stock_picking_type_action').id - menu_id = self.env['ir.model.data'].sudo().search([('name', '=', 'module_theme_treehouse')]).id - # 查询参数 - params = {'menu_id': menu_id, 'action': action_id, 'model': 'stock.picking', - 'view_type': 'kanban'} - # 拼接查询参数 - query_string = urlencode(params) - # 拼接URL - full_url = url + "/web#" + query_string - return full_url - def request_url1(self, id): url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') action_id = self.env.ref('stock.action_picking_tree_all').id diff --git a/sf_message/models/sf_message_template.py b/sf_message/models/sf_message_template.py index 8d1a2b9d..d97c5467 100644 --- a/sf_message/models/sf_message_template.py +++ b/sf_message/models/sf_message_template.py @@ -16,4 +16,6 @@ class SfMessageTemplate(models.Model): res.append('mrp.workorder') res.append('sf.maintenance.logs') res.append('quality.cnc.test') + res.append('mrp.production') + res.append('product.product') return res diff --git a/sf_message/models/sf_message_workorder.py b/sf_message/models/sf_message_workorder.py index d5186797..653562e1 100644 --- a/sf_message/models/sf_message_workorder.py +++ b/sf_message/models/sf_message_workorder.py @@ -12,19 +12,30 @@ class SFMessageWork(models.Model): _name = 'mrp.workorder' _inherit = ['mrp.workorder', 'jikimo.message.dispatch'] - @api.depends('production_availability', 'blocked_by_workorder_ids.state', 'production_id.tool_state') + @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') def _compute_state(self): super(SFMessageWork, self)._compute_state() for workorder in self: - if workorder.state == 'ready' and workorder.routing_type == '装夹预调': + min_sequence_wk = None + work_ids = workorder.production_id.workorder_ids.filtered(lambda w: w.routing_type == '装夹预调') + if work_ids: + min_sequence_wk = work_ids.sorted(key=lambda w: w.sequence)[0] + if ( + workorder.state == 'ready' and workorder.routing_type == '装夹预调' and workorder.id == min_sequence_wk.id) or ( + workorder.state == 'ready' and workorder.routing_type == '人工线下加工'): + message_template = self.env["jikimo.message.template"].sudo().search( + [("name", "=", '工单已下发通知')], limit=1) jikimo_message_queue = self.env['jikimo.message.queue'].sudo().search( - [('res_id', '=', workorder.id), ("message_status", "=", "pending")]) + [('res_id', '=', workorder.production_id.product_id.id), + ("message_status", "in", ("pending", "sent")), + ('message_template_id', '=', message_template.id)]) if not jikimo_message_queue: - workorder.add_queue('工单已下发通知') + workorder.production_id.product_id.add_queue('工单已下发通知') def _get_message(self, message_queue_ids): contents = [] - product_id = [] bussiness_node = None url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') current_time_strf = datetime.now().strftime("%Y-%m-%d %H:%M:%S") @@ -37,20 +48,7 @@ class SFMessageWork(models.Model): } i = 0 for message_queue_id in message_queue_ids: - if message_queue_id.message_template_id.name == '工单已下发通知': - content = message_queue_id.message_template_id.content - mrp_workorder_line = self.env['mrp.workorder'].sudo().search([('id', '=', int(message_queue_id.res_id))]) - mrp_workorder_list = self.env['mrp.workorder'].sudo().search( - [('product_id', '=', mrp_workorder_line.product_id.id), ('state', '=', 'ready'), - ('routing_type', '=', '装夹预调')]) - if len(mrp_workorder_list) > 0 and mrp_workorder_line.product_id.id not in product_id: - url = self.request_url() - content = content.replace('{{product_id}}', mrp_workorder_line.product_id.name).replace( - '{{number}}', str(len(mrp_workorder_list))).replace( - '{{request_url}}', url) - product_id.append(mrp_workorder_line.product_id.id) - contents.append(content) - elif message_queue_id.message_template_id.name in template_names['预警'] + template_names['已逾期']: + if message_queue_id.message_template_id.name in template_names['预警'] + template_names['已逾期']: item = message_queue_id.message_template_id bussiness_node = item.bussiness_node_id.name for reminder_time in item.reminder_time_ids: @@ -93,21 +91,7 @@ class SFMessageWork(models.Model): elif bussiness_node in template_names['已逾期']: content = content_template.replace('{{overdue_num}}', str(i)) contents.append(content) - return contents - - def request_url(self): - url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') - action_id = self.env.ref('sf_message.mrp_workorder_issued_action').id - menu_id = self.env['ir.model.data'].sudo().search([('name', '=', 'module_stock_dropshipping')]).id - active_id = self.env['mrp.workcenter'].sudo().search([('name', '=', '工件装夹中心')]).id - # 查询参数 - params = {'menu_id': menu_id, 'action': action_id, 'model': 'mrp.workorder', - 'view_type': 'list', 'active_id': active_id} - # 拼接查询参数 - query_string = urlencode(params) - # 拼接URL - full_url = url + "/web#" + query_string - return full_url + return contents, message_queue_ids def _overdue_or_warning_func(self): workorders = self.env['mrp.workorder'].search( diff --git a/sf_message/views/purchase_order_view.xml b/sf_message/views/purchase_order_view.xml new file mode 100644 index 00000000..3000d576 --- /dev/null +++ b/sf_message/views/purchase_order_view.xml @@ -0,0 +1,46 @@ + + + + 采购订单 + ir.actions.act_window + purchase.order + tree,kanban,form,pivot,graph,calendar,activity + + [('state','in',('purchase', 'done'))] + + {'search_default_filter_order_warning':1} + +

+ 还没有采购订单, 我们先创建一个! +

+

+ 当您下单到您的供应商,确定您的询价它会变成采购订单. +

+
+
+ + + 采购订单 + ir.actions.act_window + purchase.order + tree,kanban,form,pivot,graph,calendar,activity + + [('state','in',('purchase', 'done'))] + + {'search_default_filter_order_overdue':1} + +

+ 还没有采购订单, 我们先创建一个! +

+

+ 当您下单到您的供应商,确定您的询价它会变成采购订单. +

+
+
+
\ No newline at end of file diff --git a/sf_message/views/stock_picking_view.xml b/sf_message/views/stock_picking_view.xml new file mode 100644 index 00000000..9087aa18 --- /dev/null +++ b/sf_message/views/stock_picking_view.xml @@ -0,0 +1,37 @@ + + + + + stock.picking.search + stock.picking + + + + + + + + + + + + 待办 + stock.picking + ir.actions.act_window + tree,kanban,form,calendar + + {'search_default_outsourced': 1,'contact_display': 'partner_address', + 'search_default_available': 1} + + + +

+ 没有仓库调拨。 让我们创建一个! +

+

+ 移动允许您将产品从一个位置移动到另外一个位置。 +

+
+
+
\ No newline at end of file diff --git a/sf_mrs_connect/controllers/controllers.py b/sf_mrs_connect/controllers/controllers.py index 87aee0dd..85b3d5fa 100644 --- a/sf_mrs_connect/controllers/controllers.py +++ b/sf_mrs_connect/controllers/controllers.py @@ -26,7 +26,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController): ret = json.loads(ret['result']) logging.info('下发编程单:%s' % ret) domain = [('programming_no', '=', ret['programming_no'])] - if ret['manufacturing_type'] in ('scrap', 'invalid_tool_rework'): + if ret['manufacturing_type'] in ('scrap', 'invalid_tool_rework', 'rework'): domain += [('state', 'not in', ['done', 'scrap', 'cancel'])] else: domain += [('state', 'in', ['confirmed', 'pending_cam'])] @@ -56,7 +56,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController): res['message'] = '编程单号为%s的CNC程序文件从FTP拉取失败' % (ret['programming_no']) return json.JSONEncoder().encode(res) for production in productions: - production.write({'programming_state': '已编程', 'work_state': '已编程'}) + production.write({'programming_state': '已编程', 'work_state': '已编程', 'is_rework': False}) for panel in ret['processing_panel'].split(','): # 查询状态为进行中且工序类型为CNC加工的工单 cnc_workorder_has = production.workorder_ids.filtered( diff --git a/sf_plan/models/custom_plan.py b/sf_plan/models/custom_plan.py index 6ee1d3ee..5f4003da 100644 --- a/sf_plan/models/custom_plan.py +++ b/sf_plan/models/custom_plan.py @@ -13,7 +13,8 @@ class sf_production_plan(models.Model): _description = 'sf_production_plan' _inherit = ['mail.thread'] # _order = 'state desc, write_date desc' - + part_name = fields.Char('零件名称', related='product_id.part_name', readonly=True) + part_number = fields.Char('零件图号', related='product_id.part_number', readonly=True) state = fields.Selection([ ('draft', '待排程'), ('done', '已排程'), diff --git a/sf_plan/views/view.xml b/sf_plan/views/view.xml index 47e1dd54..09bd790c 100644 --- a/sf_plan/views/view.xml +++ b/sf_plan/views/view.xml @@ -17,6 +17,8 @@ decoration-danger="state == 'finished'"/> + + diff --git a/sf_plan_management/i18n/zh_CN.po b/sf_plan_management/i18n/zh_CN.po index 91b32d41..dde8ccfd 100644 --- a/sf_plan_management/i18n/zh_CN.po +++ b/sf_plan_management/i18n/zh_CN.po @@ -2023,6 +2023,11 @@ msgstr "损失" msgid "Maintenance" msgstr "" +#. module: sf_manufacturing +#: model:ir.model.fields.selection,name:sf_manufacturing.selection__mrp_production__state__confirmed +msgid "待排程" +msgstr "待排程" + #. module: mrp #: model_terms:ir.ui.view,arch_db:mrp.product_product_form_view_bom_button #: model_terms:ir.ui.view,arch_db:mrp.product_template_form_view_bom_button @@ -6678,7 +6683,7 @@ msgstr "账单状态" #: model_terms:ir.ui.view,arch_db:account.account_journal_dashboard_kanban_view #, python-format msgid "Bills" -msgstr "账单" +msgstr "发票账单" #. module: account #: model_terms:ir.ui.view,arch_db:account.account_journal_dashboard_kanban_view @@ -23023,7 +23028,7 @@ msgstr "支付:支付收据" #: model_terms:ir.ui.view,arch_db:account.view_account_payment_search #, python-format msgid "Payments" -msgstr "支付" +msgstr "付款单" #. module: account #: model_terms:ir.actions.act_window,help:account.action_account_payments diff --git a/sf_quality/models/quality_cnc_test.py b/sf_quality/models/quality_cnc_test.py index 56d924e9..9810b0e0 100644 --- a/sf_quality/models/quality_cnc_test.py +++ b/sf_quality/models/quality_cnc_test.py @@ -16,7 +16,7 @@ class SfQualityCncTest(models.Model): equipment_id = fields.Many2one(related='workorder_id.equipment_id', string='加工设备') production_line_id = fields.Many2one(related='workorder_id.production_line_id', string='生产线') - part_number = fields.Char(related='workorder_id.part_number', string='成品零件图号') + part_number = fields.Char(related='workorder_id.part_number', string='零件图号') detection_report = fields.Binary(related='workorder_id.detection_report', readonly=True, string='检测报告') state = fields.Selection([ ('waiting', '待判定'), @@ -25,7 +25,7 @@ class SfQualityCncTest(models.Model): ('pass', '合格'), ('fail', '不合格')], string='判定结果') number = fields.Integer('数量', default=1) - test_results = fields.Selection([("合格", "合格")], string="检测结果") + test_results = fields.Selection([("合格", "合格"), ("返工", "返工")], string="检测结果") reason = fields.Selection( [("programming", "编程"), ("cutter", "刀具"), ("clamping", "装夹"), ("operate computer", "操机"), ("technology", "工艺"), ("customer redrawing", "客户改图")], string="原因") diff --git a/sf_quality/views/quality_cnc_test_view.xml b/sf_quality/views/quality_cnc_test_view.xml index 29cab866..d2d37686 100644 --- a/sf_quality/views/quality_cnc_test_view.xml +++ b/sf_quality/views/quality_cnc_test_view.xml @@ -18,7 +18,8 @@ - + + + + + 检查采购单是否已逾期预警和逾期 + + code + model._overdue_or_warning_func() + 10 + minutes + -1 + + + + + + \ No newline at end of file diff --git a/sf_sale/models/sale_order.py b/sf_sale/models/sale_order.py index ca851fc6..1b282c8e 100644 --- a/sf_sale/models/sale_order.py +++ b/sf_sale/models/sale_order.py @@ -55,12 +55,15 @@ class ReSaleOrder(models.Model): store=True, readonly=False, copy=False, precompute=True, states=READONLY_FIELD_STATES, default=fields.Datetime.now) - delivery_warning = fields.Selection([('normal', '正常'), ('warning', '告警'), ('overdue', '逾期')], string='时效', - tracking=True) + delivery_warning = fields.Selection([('normal', '正常'), ('warning', '告警'), ('overdue', '逾期')], + default='normal', + string='时效', tracking=True) + + order_code = fields.Char('平台订单号', readonly=True) # 业务平台分配工厂后在智能工厂先创建销售订单 def sale_order_create(self, company_id, delivery_name, delivery_telephone, delivery_address, - deadline_of_delivery, payments_way, pay_way, state='sale'): + deadline_of_delivery, payments_way, pay_way, order_number, state='sale'): now_time = datetime.datetime.now() partner = self.get_customer() data = { @@ -76,6 +79,7 @@ class ReSaleOrder(models.Model): 'address_of_delivery': delivery_address, 'payments_way': payments_way, 'pay_way': pay_way, + 'order_code': order_number, } if deadline_of_delivery: # deadline_of_delivery字段存在为false字符串情况 @@ -158,10 +162,71 @@ class ReSaleOrder(models.Model): if not line.tax_id: raise UserError('请对【订单行】中的【税】进行选择') + consignment_purchase_order_count = fields.Integer( + "Number of consignment Purchase Order Generated", + compute='_compute_purchase_order_count') + + @api.depends('order_line.purchase_line_ids.order_id') + def _compute_purchase_order_count(self): + for order in self: + order.purchase_order_count = len(order._get_purchase_orders().filtered( + lambda po: po.purchase_type not in ['outsourcing'])) + order.consignment_purchase_order_count = len(order._get_purchase_orders().filtered( + lambda po: po.purchase_type in ['outsourcing'])) + + def action_view_purchase_orders(self): + """ + 采购 + """ + self.ensure_one() + purchase_order_ids = self._get_purchase_orders().filtered( + lambda po: po.purchase_type not in ['outsourcing']).ids + action = { + 'res_model': 'purchase.order', + 'type': 'ir.actions.act_window', + } + if len(purchase_order_ids) == 1: + action.update({ + 'view_mode': 'form', + 'res_id': purchase_order_ids[0], + }) + else: + action.update({ + 'name': _("从 %s生成采购订单", self.name), + 'domain': [('id', 'in', purchase_order_ids)], + 'view_mode': 'tree,form', + }) + return action + + def action_view_consignment_purchase_orders(self): + """ + 委外加工 + """ + self.ensure_one() + outsourcing_purchase_order_ids = self._get_purchase_orders().filtered( + lambda po: po.purchase_type in ['outsourcing']).ids + action = { + 'res_model': 'purchase.order', + 'type': 'ir.actions.act_window', + } + if len(outsourcing_purchase_order_ids) == 1: + action.update({ + 'view_mode': 'form', + 'res_id': outsourcing_purchase_order_ids[0], + }) + else: + action.update({ + 'name': _("从 %s生成委外加工订单", self.name), + 'domain': [('id', 'in', outsourcing_purchase_order_ids)], + 'view_mode': 'tree,form', + }) + return action + class ResaleOrderLine(models.Model): _inherit = 'sale.order.line' - + # part_number = fields.Char('零件图号', related='product_id.part_number', readonly=True) + part_name = fields.Char('零件名称', related='product_id.part_name', readonly=True) model_glb_file = fields.Binary('模型的glb文件', compute='_compute_model_glb_file', store=True) # product_template_id = fields.Many2one( # string="产品", @@ -199,6 +264,7 @@ class ProductTemplate(models.Model): _inherit = 'product.template' manual_quotation = fields.Boolean('人工编程', default=False) + part_name = fields.Char(string='零件名称', readonly=True) class RePurchaseOrder(models.Model): @@ -215,8 +281,37 @@ class RePurchaseOrder(models.Model): compute='_compute_user_id', store=True) - purchase_type = fields.Selection([('standard', '标准采购'), ('consignment', '委外加工')], string='采购类型', - default='standard') + purchase_type = fields.Selection( + [('standard', '标准采购'), ('consignment', '工序外协'), ('outsourcing', '委外加工'), ('outside', '外购订单')], + string='采购类型', default='standard', store=True, compute='_compute_purchase_type') + + # 合同编号 + contract_number = fields.Char(string='合同编号', size=20) + # 合同概况 + contract_summary = fields.Text(string='合同概况') + + # 选择是否为紧急采购 + urgent_purchase = fields.Selection([('no', '否'), ('yes', '是')], string='紧急采购', default='no') + + @api.depends('origin') + def _compute_purchase_type(self): + for purchase in self: + order_id = self.env['sale.order'].sudo().search([('name', '=', purchase.origin)]) + if order_id: + product_list = [line.product_id.id for line in purchase.order_line] + for order_line in order_id.order_line: + if order_line.supply_method == 'purchase': + if order_line.product_id.id in product_list: + purchase.purchase_type = 'outside' + break + elif order_line.supply_method == 'outsourcing': + if order_line.product_id.id in product_list: + purchase.purchase_type = 'outsourcing' + break + + delivery_warning = fields.Selection([('normal', '正常'), ('warning', '预警'), ('overdue', '已逾期')], + string='交期状态', default='normal', + tracking=True) @api.depends('partner_id') def _compute_user_id(self): @@ -252,21 +347,26 @@ class RePurchaseOrder(models.Model): purchase_order = pp._get_surface_technics_purchase_ids() if purchase_order: purchase_order.write({'state': 'draft'}) + pp.purchase_id = [(6, 0, [purchase_order.id])] else: server_template = self.env['product.template'].search( [('server_product_process_parameters_id', '=', pp.surface_technics_parameters_id.id), - ('detailed_type', '=', 'service')]) + ('detailed_type', '=', 'service')]) server_product_process.append((0, 0, { 'product_id': server_template.product_variant_id.id, 'product_qty': 1, 'product_uom': server_template.uom_id.id })) + # 获取服务商品最后一个供应商的采购员 + purchase_user_id = server_template.seller_ids[-1].partner_id.purchase_user_id purchase_order = self.env['purchase.order'].sudo().create({ 'partner_id': server_template.seller_ids[0].partner_id.id, 'origin': production.name, 'state': 'draft', 'purchase_type': 'consignment', - 'order_line': server_product_process}) + 'order_line': server_product_process, + 'user_id': purchase_user_id.id + }) pp.purchase_id = [(6, 0, [purchase_order.id])] # self.env.cr.commit() @@ -291,14 +391,60 @@ class RePurchaseOrder(models.Model): move_id.put_move_line() for line in item.order_line: if line.product_id.categ_type == '表面工艺': - for production_name in item.origin.split(','): - production = self.env['mrp.production'].search([('name', '=', production_name)]) - for workorder in production.workorder_ids.filtered( - lambda wd: wd.routing_type == '表面工艺' and wd.state == 'waiting' and line.product_id.server_product_process_parameters_id == wd.surface_technics_parameters_id): - workorder.state = 'ready' + if item.origin: + for production_name in item.origin.split(','): + production = self.env['mrp.production'].search([('name', '=', production_name)]) + for workorder in production.workorder_ids.filtered( + lambda wd: wd.routing_type == '表面工艺' and wd.state == 'waiting' and line.product_id.server_product_process_parameters_id == wd.surface_technics_parameters_id): + work_ids = workorder.production_id.workorder_ids.filtered( + lambda wk: wk.state not in ['done', 'rework', 'cancel']) + min_sequence_wk = min(work_ids, key=lambda wk: wk.sequence) + artificial_offline = ( + workorder.production_id.production_type == '人工线下加工' and workorder.production_id.schedule_state != '已排') + auto_production = ( + workorder.production_id.production_type == '自动化产线加工' and workorder.production_id.programming_state != '已编程') + if workorder.sequence == min_sequence_wk.sequence: + if artificial_offline or auto_production: + raise UserError('等待组件') + else: + sorted_work_ids = work_ids.sorted(key=lambda w: w.sequence) + previous_workorder = self.env['mrp.workorder'].search([('sequence', '<', workorder.sequence), + ('production_id', '=', workorder.production_id.id), + ('state', '=', 'done')], order='sequence desc', limit=1) + if not previous_workorder: + raise UserError('等待组件') + workorder.state = 'ready' return result + # # 采购订单逾期预警和已逾期 + def _overdue_or_warning_func(self): + purchase_order = self.sudo().search( + [('state', 'in', ['purchase']), ('date_planned', '!=', False), + ('receipt_status', 'in', ('partial', 'pending'))]) + last_overdue_order = None + last_warning_order = None + for item in purchase_order: + current_time = datetime.datetime.now() + if item.date_planned <= current_time: # 已逾期 + item.delivery_warning = 'overdue' + last_overdue_order = item + elif (item.date_planned - current_time).total_seconds() < 48 * 3600: # 预警 + item.delivery_warning = 'warning' + last_warning_order = item + purchase_order_done = self.sudo().search([('state', 'in', ['purchase']), ('receipt_status', '=', 'full')]) + purchase_order_overdue = purchase_order_done.filtered(lambda x: x.delivery_warning in ['overdue', 'warning']) + if purchase_order_overdue: + purchase_order_overdue.write({'delivery_warning': 'normal'}) + return last_overdue_order, last_warning_order + + +class PurchaseOrderLine(models.Model): + _inherit = 'purchase.order.line' + + part_name = fields.Char('零件名称', related='product_id.part_name', readonly=True) + # part_number = fields.Char('零件图号',related='product_id.part_number', readonly=True) + class ResPartnerToSale(models.Model): _inherit = 'res.partner' diff --git a/sf_sale/static/src/css/purchase_list.css b/sf_sale/static/src/css/purchase_list.css new file mode 100644 index 00000000..2deacb9c --- /dev/null +++ b/sf_sale/static/src/css/purchase_list.css @@ -0,0 +1,3 @@ +.purchase_order_list_name { + min-width: 62px !important; +} \ No newline at end of file diff --git a/sf_sale/views/purchase_order_view.xml b/sf_sale/views/purchase_order_view.xml index 23c71a1f..ecc061fa 100644 --- a/sf_sale/views/purchase_order_view.xml +++ b/sf_sale/views/purchase_order_view.xml @@ -9,41 +9,39 @@ 1 + + + + - + + + + sale.management.order.form.quote.inherit.sf sale.order - - 下单日期 - @@ -215,8 +241,15 @@ 订单号 - 下单时间 + 下单日期 + + hide + + + + +