diff --git a/jikimo_account_process/__manifest__.py b/jikimo_account_process/__manifest__.py index 0c1b521c..637e74b0 100644 --- a/jikimo_account_process/__manifest__.py +++ b/jikimo_account_process/__manifest__.py @@ -20,7 +20,7 @@ 'version': '0.1', # any module necessary for this one to work correctly - 'depends': ['base', 'account'], + 'depends': ['base', 'account', 'l10n_cn'], # always loaded 'data': [ diff --git a/jikimo_account_process/models/account_move.py b/jikimo_account_process/models/account_move.py index e85d4e16..94242570 100644 --- a/jikimo_account_process/models/account_move.py +++ b/jikimo_account_process/models/account_move.py @@ -1,4 +1,4 @@ -from odoo import models, fields, api +from odoo import models, fields, api, _ from odoo.exceptions import ValidationError @@ -7,6 +7,14 @@ class CustomAccountMoveLine(models.Model): _inherit = 'account.move' _description = "account move line" + fapiao = fields.Char(string='发票号', size=20, copy=False, tracking=True, required=True) + + @api.constrains('fapiao') + def _check_fapiao(self): + for record in self: + if record.fapiao and (len(record.fapiao) != 20 or not record.fapiao.isdecimal()): + raise ValidationError(_("Fapiao number is an 20-digit number. Please enter a correct one.")) + @api.model_create_multi def create(self, vals): for val in vals: diff --git a/jikimo_frontend/__manifest__.py b/jikimo_frontend/__manifest__.py index c3598323..b8b77eb1 100644 --- a/jikimo_frontend/__manifest__.py +++ b/jikimo_frontend/__manifest__.py @@ -21,8 +21,8 @@ 'web.assets_qweb': [ ], 'web.assets_backend': [ - 'jikimo_frontend/static/src/fields/custom_many2many_checkboxes/*', - 'jikimo_frontend/static/src/fields/Many2OneRadioField/*', + # 'jikimo_frontend/static/src/fields/custom_many2many_checkboxes/*', + # 'jikimo_frontend/static/src/fields/Many2OneRadioField/*', # 移除odoo相关标识 'jikimo_frontend/static/src/bye_odoo/*', 'jikimo_frontend/static/src/scss/custom_style.scss', diff --git a/jikimo_frontend/static/src/css/list_border_styles.css b/jikimo_frontend/static/src/css/list_border_styles.css index bca5499e..35fc8099 100644 --- a/jikimo_frontend/static/src/css/list_border_styles.css +++ b/jikimo_frontend/static/src/css/list_border_styles.css @@ -1,3 +1,8 @@ .o_list_renderer .o_list_table tbody > tr > td:not(.o_list_record_selector):not(.o_handle_cell):not(.o_list_button):not(.o_list_record_remove){ border:1px solid #dee2e6 !important; +} + +.custom_required_add::before{ + content: '*'; + color: red; } \ No newline at end of file diff --git a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.css b/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.css deleted file mode 100644 index 72d877a0..00000000 --- a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.css +++ /dev/null @@ -1,3 +0,0 @@ -.many2one_radio_field { - display: inline-block; -} \ No newline at end of file diff --git a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.js b/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.js deleted file mode 100644 index 8c2be97f..00000000 --- a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.js +++ /dev/null @@ -1,53 +0,0 @@ -/** @odoo-module **/ - -import { RadioField } from "@web/views/fields/radio/radio_field"; // 导入单选按钮组件 -import { registry } from "@web/core/registry"; - -export class Many2OneRadioField extends RadioField { - // 你可以重写或者添加一些方法和属性 - // 例如,你可以重写setup方法来添加一些事件监听器或者初始化一些变量 - setup() { - super.setup(); // 调用父类的setup方法 - // 你自己的代码 - } - - onImageClick(event) { - // 放大图片逻辑 - // 获取图片元素 - const img = event.target; - const close = img.nextSibling; - // 实现放大图片逻辑 - // 比如使用 CSS 放大 - img.parentElement.classList.add('zoomed'); - close.classList.add('img_close'); - } - - onCloseClick(event) { - const close = event.target; - const img = close.previousSibling; - img.parentElement.classList.remove('zoomed'); - close.classList.remove('img_close'); - } - - get items() { - return Many2OneRadioField.getItems(this.props.name, this.props.record); - } - - static getItems(fieldName, record) { - switch (record.fields[fieldName].type) { - case "selection": - return record.fields[fieldName].selection; - case "many2one": { - const value = record.preloadedData[fieldName] || []; - return value.map((item) => [item.id, item.display_name, item.image]); - } - default: - return []; - } - } -} - -Many2OneRadioField.template = "jikimo_frontend.Many2OneRadioField"; -// MyCustomWidget.supportedTypes = ['many2many']; - -registry.category("fields").add("many2one_radio", Many2OneRadioField); diff --git a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.xml b/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.xml deleted file mode 100644 index 3d797eb0..00000000 --- a/jikimo_frontend/static/src/fields/Many2OneRadioField/many2one_radio_field.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - -
- -
- -
-
-
-
- -
diff --git a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.css b/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.css deleted file mode 100644 index 20cb4c7e..00000000 --- a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.css +++ /dev/null @@ -1,100 +0,0 @@ - -.processing-capabilities-grid { - display: grid; - grid-template-columns: repeat(6, 1fr); - gap: 10px; - width: 100%; -} - -.grid-item { - display: flex; - align-items: center; -} - -.item-content { - display: flex; - flex-direction: column; - align-items: center; - text-align: center; -} -/*控制图片大小*/ -.item-icon { - width: 50px; - height: 50px; - margin-bottom: 5px; - margin-top: 15px; -} - -.item-label { - font-size: 12px; - word-break: break-word; -} - -@media (max-width: 1200px) { - .processing-capabilities-grid { - grid-template-columns: repeat(4, 1fr); - } -} - -@media (max-width: 768px) { - .processing-capabilities-grid { - grid-template-columns: repeat(3, 1fr); - } -} - -@media (max-width: 480px) { - .processing-capabilities-grid { - grid-template-columns: repeat(2, 1fr); - } -} -.image-preview-container { - position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; - background-color: rgba(0, 0, 0, 0.9); - display: flex; - justify-content: center; - align-items: center; - z-index: 1000; - opacity: 0; - transition: opacity 0.3s ease; -} - -.image-preview-container.show { - opacity: 1; -} - -.image-preview { - max-width: 90%; - max-height: 90%; - object-fit: contain; - box-shadow: 0 0 20px rgba(255, 255, 255, 0.2); - border-radius: 5px; - transform: scale(0.9); - transition: transform 0.3s ease; -} - -.image-preview-container.show .image-preview { - transform: scale(1); -} - -.image-preview-close { - position: absolute; - top: 20px; - right: 30px; - color: #fff; - font-size: 40px; - font-weight: bold; - transition: 0.3s; - cursor: pointer; - opacity: 0.7; -} - -.image-preview-close:hover, -.image-preview-close:focus { - opacity: 1; - text-decoration: none; - cursor: pointer; -} \ No newline at end of file diff --git a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.js b/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.js deleted file mode 100644 index dee78c5f..00000000 --- a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.js +++ /dev/null @@ -1,60 +0,0 @@ -/** @odoo-module **/ - -import {Many2ManyCheckboxesField} from "@web/views/fields/many2many_checkboxes/many2many_checkboxes_field"; -import {registry} from "@web/core/registry"; - -export class MyCustomWidget extends Many2ManyCheckboxesField { - setup() { - super.setup(); - } - - onImageClick(event, src) { - event.preventDefault(); - event.stopPropagation(); - - // 创建预览框 - const previewContainer = document.createElement('div'); - previewContainer.className = 'image-preview-container'; - - const previewImg = document.createElement('img'); - previewImg.src = src; - previewImg.className = 'image-preview'; - // 设置放大的预览图片大小 - previewImg.style.width = '600px'; - previewImg.style.height = 'auto'; // 保持宽高比 - - const closeButton = document.createElement('span'); - closeButton.innerHTML = '×'; - closeButton.className = 'image-preview-close'; - - previewContainer.appendChild(previewImg); - previewContainer.appendChild(closeButton); - document.body.appendChild(previewContainer); - - // 添加关闭预览的事件监听器 - const closePreview = () => { - previewContainer.classList.remove('show'); - setTimeout(() => { - document.body.removeChild(previewContainer); - }, 300); - }; - - closeButton.addEventListener('click', closePreview); - - // 点击预览框外部也可以关闭 - previewContainer.addEventListener('click', (e) => { - if (e.target === previewContainer) { - closePreview(); - } - }); - - // 使用 setTimeout 来触发过渡效果 - setTimeout(() => { - previewContainer.classList.add('show'); - }, 10); - } -} - -MyCustomWidget.template = "jikimo_frontend.MyCustomWidget"; - -registry.category("fields").add("custom_many2many_checkboxes", MyCustomWidget); \ No newline at end of file diff --git a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.xml b/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.xml deleted file mode 100644 index 9bb8797d..00000000 --- a/jikimo_frontend/static/src/fields/custom_many2many_checkboxes/custom_many2many_checkboxes.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - - -
- -
- -
- - -
-
-
-
-
-
- -
\ No newline at end of file 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 ec64e553..5d8b2fbf 100644 --- a/jikimo_frontend/static/src/js/custom_form_status_indicator.js +++ b/jikimo_frontend/static/src/js/custom_form_status_indicator.js @@ -6,8 +6,9 @@ import {_t} from "@web/core/l10n/translation"; import {FormStatusIndicator} from "@web/views/form/form_status_indicator/form_status_indicator"; import {ListRenderer} from "@web/views/list/list_renderer"; // import {StatusBarField} from "@web/views/fields/statusbar/statusbar_field"; +import {FormLabel} from "@web/views/form/form_label"; +import { fieldVisualFeedback } from "@web/views/fields/field"; -import {Field} from "@web/views/fields/field"; var Dialog = require('web.Dialog'); // var {patch} = require("web.utils") 这句话也行 @@ -51,7 +52,6 @@ const tableRequiredList = [ 'product_template_id', 'product_uom_qty', 'price_unit','product_id','product_qty', 'name', 'fault_type', 'maintenance_standards', 'Period' ] - patch(FormStatusIndicator.prototype, 'jikimo_frontend.FormStatusIndicator', { setup() { owl.onMounted(() => { @@ -62,7 +62,7 @@ patch(FormStatusIndicator.prototype, 'jikimo_frontend.FormStatusIndicator', { const dom1 = buttonsDom.children('.o_form_button_save') const dom2 = buttonsDom.children('.o_form_button_cancel') dom1.append('保存') - dom2.append('放弃保存') + dom2.append('不保存') } } catch (e) { console.log(e) @@ -107,33 +107,7 @@ patch(FormStatusIndicator.prototype, 'jikimo_frontend.FormStatusIndicator', { } ); -patch(Field.prototype, 'jikimo_frontend.Field', { - setup() { - owl.onMounted(this.setRequired); - return this._super(...arguments); - }, - setRequired() { - const id = this.props.id - const isRequired = filedRequiredList[id] - if(id == 'number_of_axles') { - console.log(isRequired) - } - if(isRequired) { - let dom; - dom = $(`label[for=${id}]`) - if(isRequired.multiple && dom.length > 1) { - dom = dom.eq(-1) - dom = dom.parent().parent().next().find('label') - } - if(isRequired.noLabel) { - dom = dom.parent().parent() - } - let t = dom.html() - t = '*' + t - dom.html(t) - } - } -}) + patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', { setup(){ owl.onMounted(() => { @@ -191,7 +165,33 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', { } } }) - +patch(FormLabel.prototype, 'jikimo_frontend.FormLabel', { + get className() { + + const { invalid, empty, readonly } = fieldVisualFeedback( + this.props.fieldInfo.FieldComponent, + this.props.record, + this.props.fieldName, + this.props.fieldInfo + ); + const classes = this.props.className ? [this.props.className] : []; + const otherRequired = filedRequiredList[this.props.fieldName] + + if(this.props.fieldInfo?.rawAttrs?.class?.indexOf('custom_required') >= 0 || otherRequired) { + classes.push('custom_required_add') + } + if (invalid) { + classes.push("o_field_invalid"); + } + if (empty) { + classes.push("o_form_label_empty"); + } + if (readonly) { + classes.push("o_form_label_readonly"); + } + return classes.join(" "); + } +}) // 根据进度条设置水印 // const statusbar_params = { @@ -231,7 +231,6 @@ $(function () { clearInterval(timer) timer = setInterval(() => { timer_count++ - const dom = $('.custom_required') let tableDom = $('.table_custom_required') if (tableDom.length) { tableDom = tableDom.eq(0).parents('tr').children('.table_custom_required') @@ -243,17 +242,6 @@ $(function () { }) clearInterval(timer) } - if (dom.length) { - dom.each(function () { - const requiredDom = $(this).parent().prev().find('label') - let t = requiredDom.html() - if (t && t.indexOf('c*') < 0) { - t = '*' + t - } - requiredDom.html(t) - }) - clearInterval(timer) - } if (timer_count == 20) { clearInterval(timer) } diff --git a/jikimo_frontend/static/src/views/list_nums/list_nums.xml b/jikimo_frontend/static/src/views/list_nums/list_nums.xml index d2ad6824..b66b9e8b 100644 --- a/jikimo_frontend/static/src/views/list_nums/list_nums.xml +++ b/jikimo_frontend/static/src/views/list_nums/list_nums.xml @@ -10,7 +10,6 @@ - diff --git a/jikimo_purchase_tier_validation/__init__.py b/jikimo_purchase_tier_validation/__init__.py index 40c32597..f553d8ff 100644 --- a/jikimo_purchase_tier_validation/__init__.py +++ b/jikimo_purchase_tier_validation/__init__.py @@ -1,5 +1,4 @@ # -*- 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 index a7bdbddf..4d676d8f 100644 --- a/jikimo_purchase_tier_validation/__manifest__.py +++ b/jikimo_purchase_tier_validation/__manifest__.py @@ -24,9 +24,6 @@ # 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 diff --git a/jikimo_purchase_tier_validation/controllers/__init__.py b/jikimo_purchase_tier_validation/controllers/__init__.py deleted file mode 100644 index 457bae27..00000000 --- a/jikimo_purchase_tier_validation/controllers/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# -*- 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 deleted file mode 100644 index 6bbe5e40..00000000 --- a/jikimo_purchase_tier_validation/controllers/controllers.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- 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 deleted file mode 100644 index 990a6418..00000000 --- a/jikimo_purchase_tier_validation/data/documents_data.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - 采购合同 - 存放采购合同相关文件 - 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 deleted file mode 100644 index cbba42ce..00000000 --- a/jikimo_purchase_tier_validation/demo/demo.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - \ No newline at end of file diff --git a/jikimo_purchase_tier_validation/models/models.py b/jikimo_purchase_tier_validation/models/models.py index 34d33d32..018766eb 100644 --- a/jikimo_purchase_tier_validation/models/models.py +++ b/jikimo_purchase_tier_validation/models/models.py @@ -21,12 +21,8 @@ class jikimo_purchase_tier_validation(models.Model): 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' res = super(jikimo_purchase_tier_validation, self).button_confirm() for record in self: if record.state == 'approved': @@ -39,45 +35,8 @@ class jikimo_purchase_tier_validation(models.Model): record.message_subscribe([record.partner_id.id]) return res - # 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 diff --git a/jikimo_purchase_tier_validation/security/ir.model.access.csv b/jikimo_purchase_tier_validation/security/ir.model.access.csv deleted file mode 100644 index 09ba96bc..00000000 --- a/jikimo_purchase_tier_validation/security/ir.model.access.csv +++ /dev/null @@ -1,2 +0,0 @@ -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/views/templates.xml b/jikimo_purchase_tier_validation/views/templates.xml deleted file mode 100644 index cea6b39a..00000000 --- a/jikimo_purchase_tier_validation/views/templates.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - - - \ No newline at end of file diff --git a/jikimo_purchase_tier_validation/views/views.xml b/jikimo_purchase_tier_validation/views/views.xml index b651e914..fdfd52c1 100644 --- a/jikimo_purchase_tier_validation/views/views.xml +++ b/jikimo_purchase_tier_validation/views/views.xml @@ -23,76 +23,10 @@ - - + + + True + + + True + + + \ No newline at end of file diff --git a/sf_manufacturing/wizard/__init__.py b/sf_manufacturing/wizard/__init__.py index 6a0af9e5..7a2541c3 100644 --- a/sf_manufacturing/wizard/__init__.py +++ b/sf_manufacturing/wizard/__init__.py @@ -5,3 +5,4 @@ from . import production_technology_wizard from . import production_technology_re_adjust_wizard from . import mrp_workorder_batch_replan_wizard from . import sf_programming_reason +from . import sale_order_cancel diff --git a/sf_manufacturing/wizard/production_technology_wizard.py b/sf_manufacturing/wizard/production_technology_wizard.py index 0415d1fd..66d7ea8d 100644 --- a/sf_manufacturing/wizard/production_technology_wizard.py +++ b/sf_manufacturing/wizard/production_technology_wizard.py @@ -3,6 +3,7 @@ import logging from itertools import groupby from odoo import models, api, fields, _ +from odoo.exceptions import UserError class ProductionTechnologyWizard(models.TransientModel): @@ -88,7 +89,10 @@ class ProductionTechnologyWizard(models.TransientModel): for item in productions: workorder = item.workorder_ids.filtered(lambda wo: wo.state not in ('cancel')).sorted( key=lambda a: a.sequence) - if workorder[0].state in ['pending']: - if workorder[0].production_id.product_id.categ_id.type == '成品' and item.programming_state != '已编程': - workorder[0].state = 'waiting' + first_element = workorder[0] if workorder else None + if not first_element: + raise UserError('工艺确认后,工单未生成,请检查配置') + if first_element.state in ['pending']: + if first_element.production_id.product_id.categ_id.type == '成品' and item.programming_state != '已编程': + first_element.state = 'waiting' return productions diff --git a/sf_manufacturing/wizard/rework_wizard.py b/sf_manufacturing/wizard/rework_wizard.py index a3ac96ed..11827819 100644 --- a/sf_manufacturing/wizard/rework_wizard.py +++ b/sf_manufacturing/wizard/rework_wizard.py @@ -35,6 +35,7 @@ class ReworkWizard(models.TransientModel): is_reprogramming = fields.Boolean(string='申请重新编程', default=False) is_reprogramming_readonly = fields.Boolean(string='申请重新编程(只读)', default=False) is_clamp_measure = fields.Boolean(string='保留装夹测量数据', default=True) + is_clamping = fields.Boolean(string='制造订单是否存在装夹预调工单') reprogramming_num = fields.Integer('重新编程次数', default=0) programming_state = fields.Selection( [('待编程', '待编程'), ('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'), diff --git a/sf_manufacturing/wizard/rework_wizard_views.xml b/sf_manufacturing/wizard/rework_wizard_views.xml index aad8a692..e6c8dea9 100644 --- a/sf_manufacturing/wizard/rework_wizard_views.xml +++ b/sf_manufacturing/wizard/rework_wizard_views.xml @@ -13,6 +13,7 @@ + -
+
保留装夹测量数据 diff --git a/sf_manufacturing/wizard/sale_order_cancel.py b/sf_manufacturing/wizard/sale_order_cancel.py new file mode 100644 index 00000000..516f8804 --- /dev/null +++ b/sf_manufacturing/wizard/sale_order_cancel.py @@ -0,0 +1,659 @@ +from odoo import models, fields, api +from odoo.exceptions import UserError + + +class SFSaleOrderCancelWizard(models.TransientModel): + _name = 'sf.sale.order.cancel.wizard' + _description = '销售订单取消向导' + + order_id = fields.Many2one('sale.order', string='销售订单') + related_docs = fields.One2many('sf.sale.order.cancel.line', 'wizard_id', string='相关单据') + has_movement = fields.Boolean(compute='_compute_has_movement', string='是否有异动') + display_message = fields.Char(compute='_compute_display_message', string='显示消息') + + @api.model + def default_get(self, fields_list): + defaults = super().default_get(fields_list) + if self._context.get('active_id'): + order = self.env['sale.order'].browse(self._context.get('active_id')) + defaults['order_id'] = order.id + # 创建向导时自动创建关联单据行 + wizard = self.create(defaults) + self.env['sf.sale.order.cancel.line'].create_from_order(wizard.id, order) + defaults['related_docs'] = wizard.related_docs.ids + return defaults + + @api.depends('related_docs.cancel_reason') + def _compute_has_movement(self): + for wizard in self: + docs_has_movement = any(doc.cancel_reason for doc in wizard.related_docs) + order_canceled = wizard.order_id.state == 'cancel' + wizard.has_movement = docs_has_movement or order_canceled + + @api.depends('has_movement', 'related_docs', 'related_docs.doc_state') + def _compute_display_message(self): + for wizard in self: + # 如果没有相关记录,显示为空 + if not wizard.related_docs: + wizard.display_message = '无下游单据' + continue + + # 检查是否所有记录都是已取消状态 + all_canceled = all(doc.doc_state == '已取消' for doc in wizard.related_docs) + if all_canceled: + wizard.display_message = '取消的下游单据如下:' + else: + wizard.display_message = '部分或全部下游单据存在异动,无法取消,详情如下:' if wizard.has_movement else '确认所有下游单据全部取消?' + + def action_confirm_cancel(self): + self.ensure_one() + + # 删除现有关联单据行 + self.related_docs.unlink() + + # 重新生成最新关联单据行 + self.env['sf.sale.order.cancel.line'].create_from_order(self.id, self.order_id) + + # 强制重新计算校验字段 + self._compute_has_movement() + self._compute_display_message() + + # 检查是否存在异动 + if self.has_movement: + raise UserError( + "存在下游单据异动,无法取消订单!\n" + "请关闭向导重新进入,以查看最新状态!" + ) + + # 取消销售订单关联的采购单 + purchase_orders = self.env['purchase.order'].search([ + ('origin', '=', self.order_id.name) + ]) + if purchase_orders: + purchase_orders.write({'state': 'cancel'}) + + # 取消销售订单 + result = self.order_id.action_cancel() + + # 取消关联的制造订单及其采购单 + manufacturing_orders = self.env['mrp.production'].search([ + ('origin', '=', self.order_id.name) + ]) + for mo in manufacturing_orders: + # 取消制造订单关联的采购单,但保持关联关系 + mo_purchase_orders = self.env['purchase.order'].search([ + ('origin', '=', mo.name) + ]) + if mo_purchase_orders: + mo_purchase_orders.write({'state': 'cancel'}) + + # 取消制造订单的质检单 + mo_quality_checks = self.env['quality.check'].search([ + ('production_id', '=', mo.id) + ]) + if mo_quality_checks: + mo_quality_checks.write({'quality_state': 'cancel'}) + + # 取消制造订单的子制造订单 + child_mo_ids = self.env['mrp.production'].search([ + ('origin', '=', mo.name) + ]) + + if child_mo_ids: + # child_mo_ids |= mo.child_ids + # for child_mo in child_mo_ids: + for child_mo in child_mo_ids: + child_mo.action_cancel() + + # 取消工单的外协单 + for workorder in mo.workorder_ids: + if workorder.picking_ids: + for pkd in workorder.picking_ids: + pkd.write({'state': 'cancel'}) + + # 取消制造订单 + mo.action_cancel() + + # 取消制造订单关联的编程单 + mo._change_programming_state() + + # 取消组件的制造单关联的采购单 + for comp_mo in self.env['mrp.production'].search([ + ('origin', '=', mo.name) + ]): + comp_purchase_orders = self.env['purchase.order'].search([ + ('origin', '=', comp_mo.name) + ]) + if comp_purchase_orders: + comp_purchase_orders.button_cancel() + + return result + + +class SFSaleOrderCancelLine(models.TransientModel): + _name = 'sf.sale.order.cancel.line' + _description = '销售订单取消行' + + wizard_id = fields.Many2one('sf.sale.order.cancel.wizard') + sequence = fields.Integer('序号') + category = fields.Char('大类') + doc_name = fields.Char('单据名称') + operation_type = fields.Char('作业类型') + doc_number = fields.Char('单据编号') + line_number = fields.Char('行号') + product_name = fields.Char('产品名称') + quantity = fields.Float('数量') + doc_state = fields.Char('单据状态') + cancel_reason = fields.Char('禁止取消原因') + + quantity_str = fields.Char( + string="数量(字符串)", + compute="_compute_quantity_str", + store=False, # 默认不存储,除非需要搜索/排序 + ) + + @api.depends("quantity") + def _compute_quantity_str(self): + for record in self: + # 处理所有可能的 False/0 情况 + record.quantity_str = str(int(record.quantity)) if record.quantity not in [False, 0] else "" + + @api.model + def create_from_order(self, wizard_id, order): + sequence = 1 + lines = [] + map_dict = { + 'waiting': '等待其他作业', + 'to approve': '待批准', + 'technology_to_confirmed': '待工艺确认', + 'confirmed': '已确认', + 'pending': '等待其他工单', + 'none': '待处理', + 'draft': '询价', + 'cancel': '已取消', + 'pass': '通过的', + 'fail': '失败的', + 'done': '已完成', + 'rework': '返工', + 'purchase': '采购订单', + 'ready': '就绪', + 'approved': '已批准', + 'pending_cam': '待加工', + 'progress': '加工中', + 'assigned': '就绪' + } + + module_name_dict = { + 'purchase': '采购', + 'quality': '质量', + 'mrp': '制造', + 'stock': '库存', + 'account': '会计', + 'hr': '员工', + 'project': '项目', + 'crm': '销售', + 'point_of_sale': '销售', + 'website': '网站', + 'sf_plan': '计划', + } + + # 检查销售订单 + if order.invoice_ids: + a = 0 + for invoice in order.invoice_ids: + a += 1 + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': '销售', + 'doc_name': '销售订单', + 'operation_type': '', + 'doc_number': invoice.name, + 'line_number': a, + 'product_name': invoice.product_id.name, + 'quantity': invoice.quantity, + 'doc_state': invoice.state, + 'cancel_reason': '已有异动' if invoice.state != 'draft' else '' + } + lines.append(self.create(vals)) + sequence += 1 + + # 检查交货单 + if order.picking_ids: + for picking in order.picking_ids: + b = 0 + for move in picking.move_ids: + b += 1 + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + # 'category': '库存', + 'category': module_name_dict[picking._original_module], + # 'doc_name': '交货单', + 'doc_name': picking._description, + 'operation_type': picking.picking_type_id.name, + 'doc_number': picking.name, + 'line_number': b, + 'product_name': f'[{move.product_id.default_code}] {move.product_id.name}' if move else '', + # 'quantity': picking.product_qty if hasattr(picking, 'product_qty') else 0, + 'quantity': move.product_uom_qty, + 'doc_state': map_dict.get(picking.state, picking.state), + 'cancel_reason': '已有异动' if picking.state not in ['draft', 'cancel', 'waiting'] else '' + } + lines.append(self.create(vals)) + sequence += 1 + + # # 成品质检单 + # fin_quality_checks = self.env['quality.check'].search([ + # ('picking_id', '=', picking.id) + # ]) + # if fin_quality_checks: + # b1 = 0 + # for fin_qc in fin_quality_checks: + # b1 += 1 + # vals = { + # 'wizard_id': wizard_id, + # 'sequence': sequence, + # 'category': '制造', + # 'doc_name': '质检单', + # 'operation_type': '', + # 'doc_number': fin_qc.name, + # 'line_number': b1, + # 'product_name': f'[{fin_qc.product_id.default_code}] {fin_qc.product_id.name}', + # 'quantity': 1, + # 'doc_state': map_dict.get(fin_qc.quality_state, fin_qc.quality_state), + # 'cancel_reason': '已有异动' if fin_qc.quality_state not in ['none', 'cancel', 'waiting'] else '' + # } + # lines.append(self.create(vals)) + + # 检查所有的质检单 + quality_checks = self.env['quality.check'].search([ + ('product_id.name', 'like', f'%{order.name}%')]) + if quality_checks: + b1 = 0 + for quality_check in quality_checks: + b1 += 1 + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': module_name_dict[quality_check._original_module], + 'doc_name': quality_check._description, + 'operation_type': '', + 'doc_number': f'{quality_check.name}-{quality_check.title}', + 'line_number': 1, + 'product_name': f'[{quality_check.product_id.default_code}] {quality_check.product_id.name}' if quality_check.product_id.default_code else quality_check.product_id.name, + 'quantity': 1, + 'doc_state': map_dict.get(quality_check.quality_state, quality_check.quality_state), + 'cancel_reason': '已有异动' if quality_check.quality_state not in ['none', 'cancel', 'waiting'] else '' + } + lines.append(self.create(vals)) + + # 检查组件的制造单 + # component_mos = self.env['mrp.production'].search([ + # ('origin', '=', mo.name)]) + component_mos = self.env['mrp.production'].search([ + ('product_id.name', 'like', f'%R-{order.name}%')]) + h = 0 + if component_mos: + for comp_mo in component_mos: + h += 1 + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': module_name_dict[comp_mo._original_module], + 'doc_name': comp_mo._description, + 'operation_type': '', + 'doc_number': comp_mo.name, + 'line_number': h, + 'product_name': f'{comp_mo.product_id.name}', + 'quantity': comp_mo.product_qty, + 'doc_state': map_dict.get(comp_mo.state, comp_mo.state), + 'cancel_reason': '已有异动' if comp_mo.state not in ['technology_to_confirmed', + 'cancel'] else '' + } + lines.append(self.create(vals)) + sequence += 1 + + for pinking_id in comp_mo.picking_ids: + y = 0 + for move in pinking_id.move_ids: + y += 1 + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': module_name_dict[pinking_id._original_module], + 'doc_name': pinking_id._description, + 'doc_number': f'{comp_mo.name}-{pinking_id.name}', + 'line_number': y, + 'operation_type': pinking_id.picking_type_id.name, + 'product_name': move.product_id.name if move.product_id else '', + 'quantity': move.product_uom_qty, + 'doc_state': map_dict.get(pinking_id.state, pinking_id.state), + 'cancel_reason': '已有异动' if pinking_id.state not in ['cancel', 'waiting', + 'assigned'] else '' + } + lines.append(self.create(vals)) + + # 检查销售订单直接关联的采购单 + purchase_orders = self.env['purchase.order'].search([ + ('origin', 'like', f'%{order.name}%') + ]) + if purchase_orders: + c = 0 + for po in purchase_orders: + for order_line in po.order_line: + c += 1 + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': module_name_dict[po._original_module], + 'doc_name': po._description, + 'operation_type': '', + 'doc_number': po.name, + 'line_number': c, + 'product_name': f'[{order_line.product_id.default_code}] {order_line.product_id.name}', + 'quantity': order_line.product_qty if order_line else 0, + 'doc_state': map_dict.get(po.state, po.state), + 'cancel_reason': '已有异动' if po.state not in ['draft', 'cancel'] else '' + } + lines.append(self.create(vals)) + sequence += 1 + + # 客供料的入库单 + for pod in purchase_orders: + pkds = self.env['stock.picking'].search([ + ('origin', '=', pod.name) + ]) + if pkds: + for pkd in pkds: + x3 = 0 + for move in pkd.move_ids: + x3 += 1 + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': module_name_dict[pkd._original_module], + 'doc_name': pkd._description, + 'doc_number': pkd.name, + 'line_number': x3, + 'operation_type': pkd.picking_type_id.name, + 'product_name': f'[{move.product_id.default_code}] {move.product_id.name}', + 'quantity': move.product_uom_qty, + 'doc_state': map_dict.get(pkd.state, pkd.state), + 'cancel_reason': '已有异动' if pkd.state not in ['waiting', 'cancel', 'confirmed'] else '' + } + lines.append(self.create(vals)) + + # + for child_pkd in self.env['stock.picking'].search([ + ('origin', '=', pkd.name) + ]): + x4 = 0 + for child_move in child_pkd.move_ids: + x4 += 1 + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': module_name_dict[child_pkd._original_module], + 'doc_name': child_pkd._description, + 'doc_number': child_pkd.name, + 'line_number': x4, + 'operation_type': child_pkd.picking_type_id.name, + 'product_name': child_move.product_id.name if child_move.product_id else '', + 'quantity': child_move.product_uom_qty, + 'doc_state': map_dict.get(child_pkd.state, child_pkd.state), + 'cancel_reason': '已有异动' if child_pkd.state not in ['waiting', + 'cancel', 'confirmed'] else '' + } + lines.append(self.create(vals)) + + # 检查制造订单 + manufacturing_orders = self.env['mrp.production'].search([ + ('origin', '=', order.name) + ]) + d = 0 + # 在领料单处只进行一次 + flag = True + program_list = [] + for mo in manufacturing_orders: + # 添加制造订单本身 + d += 1 + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': module_name_dict[mo._original_module], + 'doc_name': mo._description, + 'doc_number': mo.name, + 'operation_type': '', + 'line_number': d, + 'product_name': f'[{mo.product_id.default_code}] {mo.product_id.name}', + 'quantity': mo.product_qty, + 'doc_state': map_dict.get(mo.state, mo.state), + 'cancel_reason': '已有异动' if mo.state not in ['technology_to_confirmed', 'cancel'] else '' + } + lines.append(self.create(vals)) + sequence += 1 + + # 检查制造订单关联的采购单 + purchase_orders = self.env['purchase.order'].search([ + ('origin', 'like', f'%{mo.name}%') + ]) + if purchase_orders: + e = 0 + for po in purchase_orders: + for order_line in po.order_line: + e += 1 + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': module_name_dict[po._original_module], + 'doc_name': po._description, + 'doc_number': po.name, + 'line_number': e, + 'operation_type': '', + 'product_name': order_line.product_id.name if order_line else '', + 'quantity': order_line.product_qty if order_line else 0, + 'doc_state': map_dict.get(po.state, po.state), + 'cancel_reason': '已有异动' if po.state not in ['draft', 'cancel'] else '' + } + lines.append(self.create(vals)) + sequence += 1 + + # 制造询价单的入库单 + for pod in purchase_orders: + pkds = self.env['stock.picking'].search([ + ('origin', '=', pod.name) + ]) + if pkds: + for pkd in pkds: + x1 = 0 + for move in pkd.move_ids: + x1 += 1 + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': module_name_dict[pkd._original_module], + 'doc_name': pkd._description, + 'doc_number': pkd.name, + 'line_number': x1, + 'operation_type': pkd.picking_type_id.name, + 'product_name': move.product_id.name if move.product_id else '', + 'quantity': move.product_uom_qty, + 'doc_state': map_dict.get(pkd.state, pkd.state), + 'cancel_reason': '已有异动' if pkd.state not in ['draft', 'cancel'] else '' + } + lines.append(self.create(vals)) + + # + for child_pkd in self.env['stock.picking'].search([ + ('origin', '=', pkd.name) + ]): + x2 = 0 + for child_move in child_pkd.move_ids: + x2 += 1 + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': module_name_dict[child_pkd._original_module], + 'doc_name': child_pkd._description, + 'doc_number': child_pkd.name, + 'line_number': x2, + 'operation_type': child_pkd.picking_type_id.name, + 'product_name': child_move.product_id.name if child_move.product_id else '', + 'quantity': child_move.product_uom_qty, + 'doc_state': map_dict.get(child_pkd.state, child_pkd.state), + 'cancel_reason': '已有异动' if child_pkd.state not in ['draft', 'cancel'] else '' + } + lines.append(self.create(vals)) + + # 检查制造订单的领料单 + + if mo.picking_ids and flag: + for picking in mo.picking_ids: + f = 0 + for move in picking.move_ids: + f += 1 + is_changed = False + if picking.state not in ['draft', 'cancel', 'waiting']: + is_changed = True + if picking.picking_type_id.name == '客供料入库' and picking.state in ['cancel', 'assigned']: + is_changed = False + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': module_name_dict[picking._original_module], + 'doc_name': picking._description, + 'doc_number': picking.name, + 'line_number': f, + 'operation_type': picking.picking_type_id.name, + 'product_name': move.product_id.name if move.product_id else '', + 'quantity': move.product_uom_qty, + 'doc_state': map_dict.get(picking.state, picking.state), + 'cancel_reason': '已有异动' if is_changed else '' + } + lines.append(self.create(vals)) + sequence += 1 + flag = False + + # 检查制造订单的工单 + if mo.workorder_ids: + g = 0 + for workorder in mo.workorder_ids: + g += 1 + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': module_name_dict[workorder._original_module], + 'doc_name': workorder._description, + 'doc_number': f'{mo.name}-{workorder.processing_panel}-{workorder.name}' if workorder.processing_panel else f'{mo.name}-{workorder.name}', + 'line_number': g, + 'operation_type': '', + 'product_name': f'[{mo.product_id.default_code}] {mo.product_id.name}', + 'quantity': workorder.qty_production, + 'doc_state': map_dict.get(workorder.state, workorder.state), + 'cancel_reason': '已有异动' if workorder.state not in ['draft', 'cancel', 'pending', + 'waiting'] else '' + } + lines.append(self.create(vals)) + sequence += 1 + + # 工艺外协处理 + if workorder.picking_ids: + for pkd in workorder.picking_ids: + z = 0 + for move in pkd.move_ids: + z += 1 + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': module_name_dict[pkd._original_module], + 'doc_name': pkd._description, + 'doc_number': f'{mo.name}-{workorder.name}-{pkd.name}', + 'line_number': z, + 'operation_type': pkd.picking_type_id.name, + 'product_name': move.product_id.name if move.product_id else '', + 'quantity': move.product_uom_qty, + 'doc_state': map_dict.get(pkd.state, pkd.state), + 'cancel_reason': '已有异动' if pkd.state not in ['cancel', 'waiting'] else '' + } + lines.append(self.create(vals)) + + # # 检查制造订单组件的采购单和制造单 + # for move in mo.move_raw_ids: + # # 检查组件的采购单 + # component_pos = self.env['purchase.order'].search([ + # ('origin', '=', mo.name), + # ('order_line.product_id', '=', move.product_id.id) + # ]) + # for po in component_pos: + # vals = { + # 'wizard_id': wizard_id, + # 'sequence': sequence, + # 'category': '制造', + # 'doc_name': '组件采购单', + # 'operation_type': '组件采购', + # 'doc_number': po.name, + # 'product_name': move.product_id.name, + # 'quantity': po.order_line[0].product_qty if po.order_line else 0, + # 'doc_state': po.state, + # 'cancel_reason': '已有异动' if po.state not in ['draft', 'cancel'] else '' + # } + # lines.append(self.create(vals)) + # sequence += 1 + + # # 检查制造订单的质检单 + # quality_checks = self.env['quality.check'].search([ + # ('production_id', '=', mo.id) + # ]) + # if quality_checks: + # i = 0 + # for check in quality_checks: + # i += 1 + # vals = { + # 'wizard_id': wizard_id, + # 'sequence': sequence, + # 'category': '制造', + # 'doc_name': '质检单', + # 'operation_type': '', + # 'doc_number': check.name, + # 'line_number': i, + # 'product_name': f'[{check.product_id.default_code}] {check.product_id.name}', + # 'quantity': 1, + # 'doc_state': map_dict.get(check.quality_state, check.quality_state), + # 'cancel_reason': '已有异动' if check.quality_state not in ['none', 'cancel', 'waiting'] else '' + # } + # lines.append(self.create(vals)) + # sequence += 1 + + # 检查制造订单的编程单 + cloud_programming = mo._cron_get_programming_state() + if cloud_programming: + programming_no = cloud_programming['programming_no'] + + # 检查当前lines中是否已存在相同doc_number的记录 + if not any(line.doc_number == programming_no for line in lines): + vals = { + 'wizard_id': wizard_id, + 'sequence': sequence, + 'category': '编程', + 'doc_name': '编程单', + 'operation_type': '', + 'doc_number': programming_no, # 直接使用变量 + 'line_number': 1, + 'product_name': '', + 'quantity': 0, + 'doc_state': cloud_programming['programming_state'], + 'cancel_reason': '' + } + lines.append(self.create(vals)) + + return lines + + # unique_lines = {} + # for line in lines: + # doc_number = line.doc_number + # if doc_number not in unique_lines: + # unique_lines[doc_number] = line + # + # # 返回去重后的记录列表 + # return list(unique_lines.values()) diff --git a/sf_manufacturing/wizard/sale_order_cancel_views.xml b/sf_manufacturing/wizard/sale_order_cancel_views.xml new file mode 100644 index 00000000..134c524b --- /dev/null +++ b/sf_manufacturing/wizard/sale_order_cancel_views.xml @@ -0,0 +1,69 @@ + + + + sf.sale.order.cancel.wizard.form + sf.sale.order.cancel.wizard + +
+ + + + + + + + + + + + + + + + + + + +
+
+
+
+
+ + + sf.sale.order.cancel.line.form + sf.sale.order.cancel.line + +
+ + + + + + + + + + + + + + + + + +
+
+
+ +
\ No newline at end of file diff --git a/sf_manufacturing/wizard/workpiece_delivery_wizard.py b/sf_manufacturing/wizard/workpiece_delivery_wizard.py index 6a13fc08..c41a7619 100644 --- a/sf_manufacturing/wizard/workpiece_delivery_wizard.py +++ b/sf_manufacturing/wizard/workpiece_delivery_wizard.py @@ -56,7 +56,6 @@ class WorkpieceDeliveryWizard(models.TransientModel): def _get_agv_route_type_selection(self): return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection'] - delivery_type = fields.Selection(selection=_get_agv_route_type_selection, string='类型') def dispatch_confirm(self): diff --git a/sf_message/data/bussiness_node.xml b/sf_message/data/bussiness_node.xml index b429d766..14115e27 100644 --- a/sf_message/data/bussiness_node.xml +++ b/sf_message/data/bussiness_node.xml @@ -68,6 +68,11 @@ stock.picking + + 调拨单质检完成提醒 + stock.picking + + 装夹预调工单逾期预警 mrp.workorder @@ -156,4 +161,12 @@ product.product + + + + 计划数据异常跟踪 + mrp.workorder + + + \ No newline at end of file diff --git a/sf_message/data/template_data.xml b/sf_message/data/template_data.xml index 11f05a3c..63e51101 100644 --- a/sf_message/data/template_data.xml +++ b/sf_message/data/template_data.xml @@ -252,6 +252,18 @@ ### 订单发货提醒: 单号:发料出库单[{{name}}]({{request_url}}) 事项:销售订单{{sale_order_name}}已全部产出并入库,请及时发货 + + + + 调拨单质检完成提醒 + + stock.picking + + markdown + normal + ### {{picking_type_name}}待处理提醒: +单号:[{{name}}]({{request_url}}) +事项:质量检查已完成 @@ -402,4 +414,19 @@ 事项:有{{num}}个质检单需要处理。 + + + + 计划数据异常跟踪 + + mrp.workorder + + markdown + normal + ### 工单计划数据异常删除: +工单号:{{name}} +异动时间:{{write_date}} + + + \ No newline at end of file diff --git a/sf_message/models/sf_message_product.py b/sf_message/models/sf_message_product.py index 0f2cfd7e..302aa5a0 100644 --- a/sf_message/models/sf_message_product.py +++ b/sf_message/models/sf_message_product.py @@ -16,6 +16,7 @@ class SFMessageProduct(models.Model): mrp_production_list = self.env['mrp.production'].sudo().search( [('product_id', '=', product_product.id)]) production_num = 0 + routing_type = None 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( @@ -23,7 +24,7 @@ class SFMessageProduct(models.Model): if mrp_production_ready: production_num += 1 if production_num >= 1: - url = self.get_request_url() + url = self.get_request_url(routing_type) content = content.replace('{{product_id}}', product_product.name).replace( '{{number}}', str(production_num)).replace( '{{request_url}}', url) @@ -42,11 +43,15 @@ class SFMessageProduct(models.Model): contents.append(content) return contents, message_queue_ids - def get_request_url(self): + def get_request_url(self, routing_type): 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 + if routing_type == '人工线下加工': + routing_name = '线下工作中心' + else: + routing_name = '工件装夹中心' + active_id = self.env['mrp.workcenter'].sudo().search([('name', '=', routing_name)]).id # 查询参数 params = {'menu_id': menu_id, 'action': action_id, 'model': 'mrp.workorder', 'view_type': 'list', 'active_id': active_id} diff --git a/sf_message/models/sf_message_stock_picking.py b/sf_message/models/sf_message_stock_picking.py index a2c17407..c6f91b93 100644 --- a/sf_message/models/sf_message_stock_picking.py +++ b/sf_message/models/sf_message_stock_picking.py @@ -9,6 +9,8 @@ class SFMessageStockPicking(models.Model): _description = "库存调拨" _inherit = ['stock.picking', 'jikimo.message.dispatch'] + quality_check_ids = fields.One2many('quality.check', 'picking_id', '质量检测单') + @api.model_create_multi def create(self, vals): result = super(SFMessageStockPicking, self).create(vals) @@ -20,14 +22,17 @@ class SFMessageStockPicking(models.Model): logging.info('add_queue调拨入库 error:%s' % e) return result - @api.depends('move_type', 'immediate_transfer', 'move_ids.state', 'move_ids.picking_id') + @api.depends('move_type', 'immediate_transfer', 'move_ids.state', 'move_ids.picking_id', + 'quality_check_ids.quality_state') def _compute_state(self): super(SFMessageStockPicking, self)._compute_state() 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('坯料发料提醒') + jikimo_message_queue = record.get_message_queue(record.id) + if not jikimo_message_queue: + record.add_queue('坯料发料提醒') if record.picking_type_id.sequence_code == 'SFP' and record.state == 'done': stock_picking_sfp = record.env['stock.picking'].search( @@ -48,6 +53,14 @@ class SFMessageStockPicking(models.Model): 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('工序外协发料通知') + if record.quality_check_ids and all( + qc.quality_state in ['pass', 'fail'] for qc in record.quality_check_ids): + message_template_id = self.env["jikimo.message.template"].sudo().search( + [('name', '=', '调拨单质检完成提醒')]) + stock_picking_send = self.env["jikimo.message.queue"].sudo().search( + [('res_id', '=', record.id), ('message_template_id', '=', message_template_id.id)]) + if not stock_picking_send: + record.add_queue('调拨单质检完成提醒') except Exception as e: logging.info('add_queue_compute_state error:%s' % e) @@ -83,6 +96,17 @@ class SFMessageStockPicking(models.Model): content = self.deal_stock_picking_sfp(message_queue_id) if content: contents.append(content) + elif 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))]) + 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('{{picking_type_name}}', stock_picking_line.picking_type_id.name).replace( + '{{name}}', stock_picking_line.name).replace('{{request_url}}', url_with_id) + contents.append(content) return contents, message_queue_ids def get_special_url(self, id, tmplate_name, special_name, model_id): @@ -107,3 +131,14 @@ class SFMessageStockPicking(models.Model): # 拼接URL full_url = url + "/web#" + query_string return full_url + + def get_message_queue(self, res_id): + business_node_id = self.env.ref('sf_message.bussiness_material_picking_remind').id + message_template = self.env["jikimo.message.template"].sudo().search([ + ("model", "=", self._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_workorder.py b/sf_message/models/sf_message_workorder.py index 653562e1..42b03139 100644 --- a/sf_message/models/sf_message_workorder.py +++ b/sf_message/models/sf_message_workorder.py @@ -188,3 +188,10 @@ class SFMessageWork(models.Model): ]) if message_queue_ids: message_queue_ids.write({'message_status': 'cancel'}) + + def write(self, vals): + res = super(SFMessageWork, self).write(vals) + if ('leave_id' in vals and vals['leave_id'] is False or 'date_planned_start' in vals and vals['date_planned_start'] is False) \ + and self.schedule_state != '未排': + self.add_queue('计划数据异常跟踪') + return res diff --git a/sf_mrs_connect/controllers/controllers.py b/sf_mrs_connect/controllers/controllers.py index 8c2c7be9..7599ed6c 100644 --- a/sf_mrs_connect/controllers/controllers.py +++ b/sf_mrs_connect/controllers/controllers.py @@ -61,7 +61,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController): for panel in ret['processing_panel'].split(','): # 查询状态为进行中且工序类型为CNC加工的工单 cnc_workorder_has = production.workorder_ids.filtered( - lambda ach: ach.routing_type == 'CNC加工' and ach.state not in ['progress', 'done', + lambda ach: ach.routing_type in ['CNC加工', '人工线下加工'] and ach.state not in ['progress', 'done', 'rework', 'cancel'] and ach.processing_panel == panel) if cnc_workorder_has: @@ -76,7 +76,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController): for panel in ret['processing_panel'].split(','): # 查询状态为进行中且工序类型为CNC加工的工单 cnc_workorder = productions.workorder_ids.filtered( - lambda ac: ac.routing_type == 'CNC加工' and ac.state not in ['progress', 'done', 'rework' + lambda ac: ac.routing_type in ['CNC加工', '人工线下加工'] and ac.state not in ['progress', 'done', 'rework' 'cancel'] and ac.processing_panel == panel) if cnc_workorder: # program_path_tmp_panel = os.path.join('C://Users//43484//Desktop//fsdownload//test', @@ -91,19 +91,21 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController): logging.info('panel_file_path:%s' % panel_file_path) cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())}) pre_workorder = productions.workorder_ids.filtered( - lambda ap: ap.routing_type == '装夹预调' and ap.state not in ['done', 'rework' + lambda ap: ap.routing_type in ['装夹预调', '人工线下加工'] and ap.state not in ['done', 'rework' 'cancel'] and ap.processing_panel == panel) if pre_workorder: pre_workorder.write( {'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())}) productions.write({'programming_state': '已编程', 'work_state': '已编程'}) + productions.filtered(lambda p: p.production_type == '人工线下加工').write({'manual_quotation': True}) logging.info('已更新制造订单编程状态:%s' % productions.ids) # 对制造订单所有面的cnc工单的程序用刀进行校验 try: logging.info(f'已更新制造订单:{productions}') re_tool_chekout = False - re_tool_chekout = productions.production_cnc_tool_checkout() + productions_temp = productions.filtered(lambda p: p.production_type == '自动化产线加工') + re_tool_chekout = productions_temp.production_cnc_tool_checkout() if re_tool_chekout: return json.JSONEncoder().encode({'status': -3, 'message': '对cnc工单的程序用刀进行校验失败'}) except Exception as e: @@ -200,6 +202,17 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController): 'send_time': ret['send_time'], }) logging.info('已创建无效功能刀具的编程记录:%s' % production.name) + elif ret['reprogramming_reason']: + production.programming_record_ids.create({ + 'number': len(production.programming_record_ids) + 1, + 'production_id': production.id, + 'reason': ret['reprogramming_reason'], + 'programming_method': ret['programme_way'], + 'current_programming_count': ret['reprogramming_num'], + 'target_production_id': productions_reprogram, + 'apply_time': ret['trigger_time'], + 'send_time': ret['send_time'], + }) else: logging.info('无对应状态,不需更新编程记录') diff --git a/sf_mrs_connect/models/sync_common.py b/sf_mrs_connect/models/sync_common.py index 835183b3..b4a05353 100644 --- a/sf_mrs_connect/models/sync_common.py +++ b/sf_mrs_connect/models/sync_common.py @@ -1968,8 +1968,7 @@ class CuttingSpeed(models.Model): self.create({ 'name': item['name'], 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( - [('code', '=', item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', item['standard_library_code'])]).id, 'execution_standard_id': self.env['sf.international.standards'].search( [('code', '=', item['execution_standard_code'])]).id, 'material_name_id': self.env['sf.materials.model'].search( @@ -1988,6 +1987,8 @@ class CuttingSpeed(models.Model): }) else: cutting_speed.write({ + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', item['standard_library_code'])]).id, 'execution_standard_id': self.env['sf.international.standards'].search( [('code', '=', item['execution_standard_code'])]).id, 'material_name_id': self.env['sf.materials.model'].search( @@ -2020,8 +2021,7 @@ class CuttingSpeed(models.Model): self.create({ 'name': item['name'], 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( - [('code', '=', item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', item['standard_library_code'])]).id, 'execution_standard_id': self.env['sf.international.standards'].search( [('code', '=', item['execution_standard_code'])]).id, 'material_name_id': self.env['sf.materials.model'].search( @@ -2040,6 +2040,8 @@ class CuttingSpeed(models.Model): }) else: cutting_speed.write({ + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', item['standard_library_code'])]).id, 'execution_standard_id': self.env['sf.international.standards'].search( [('code', '=', item['execution_standard_code'])]).id, 'material_name_id': self.env['sf.materials.model'].search( @@ -2118,8 +2120,7 @@ class CuttingSpeed(models.Model): self.create({ 'name': item['name'], 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( - [('code', '=', item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', item['standard_library_code'])]).id, 'materials_type_id': self.env['sf.materials.model'].search( [('materials_no', '=', item['materials_type_code'])]).id, 'cutting_width_depth_id': self.env['sf.cutting.width.depth'].search( @@ -2130,6 +2131,8 @@ class CuttingSpeed(models.Model): }) else: feed_per_tooth.write({ + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', item['standard_library_code'])]).id, 'materials_type_id': self.env['sf.materials.model'].search( [('materials_no', '=', item['materials_type_code'])]).id, 'cutting_width_depth_id': self.env['sf.cutting.width.depth'].search( @@ -2156,8 +2159,7 @@ class CuttingSpeed(models.Model): self.create({ 'name': item['name'], 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( - [('code', '=', item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', item['standard_library_code'])]).id, 'materials_type_id': self.env['sf.materials.model'].search( [('materials_no', '=', item['materials_type_code'])]).id, 'cutting_width_depth_id': self.env['sf.cutting.width.depth'].search( @@ -2168,6 +2170,8 @@ class CuttingSpeed(models.Model): }) else: feed_per_tooth.write({ + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', item['standard_library_code'])]).id, 'materials_type_id': self.env['sf.materials.model'].search( [('materials_no', '=', item['materials_type_code'])]).id, 'cutting_width_depth_id': self.env['sf.cutting.width.depth'].search( @@ -2195,7 +2199,7 @@ class Cutting_tool_standard_library(models.Model): if result['status'] == 1: for item in result['cutting_tool_standard_library_yesterday_list']: cutting_tool_standard_library = self.search( - [("code", '=', item['code'].replace("JKM", result['factory_short_name'])), + [("code", '=', item['code']), ('active', 'in', [True, False])]) cutting_tool_type = self.env['sf.cutting.tool.type'].search( [("code", '=', item['cutting_tool_type_code'])]) @@ -2206,7 +2210,7 @@ class Cutting_tool_standard_library(models.Model): brand = self.env['sf.machine.brand'].search([("code", '=', item['brand_code'])]) if not cutting_tool_standard_library: self.create({ - "code": item['code'].replace("JKM", result['factory_short_name']), + "code": item['code'], "name": item['name'], "cutting_tool_material_id": cutting_tool_material.id, "cutting_tool_type_id": cutting_tool_type.id, @@ -2228,9 +2232,9 @@ class Cutting_tool_standard_library(models.Model): 'maintenance.equipment.image'].search( [('name', '=', item['fit_blade_shape'])]).id, "chuck_id": False if not item['chuck_code'] else self.search( - [('code', '=', item['chuck_code'].replace("JKM", result['factory_short_name']))]).id, + [('code', '=', item['chuck_code'])]).id, "handle_id": False if not item['handle_code'] else self.search( - [('code', '=', item['handle_code'].replace("JKM", result['factory_short_name']))]).id, + [('code', '=', item['handle_code'])]).id, "suitable_machining_method_ids": [(6, 0, [])] if not item.get( 'suitable_machining_methods') else self.env['maintenance.equipment.image']._get_ids( item['suitable_machining_methods']), @@ -2270,9 +2274,9 @@ class Cutting_tool_standard_library(models.Model): 'maintenance.equipment.image'].search( [('name', '=', item['fit_blade_shape'])]).id, "chuck_id": False if not item['chuck_code'] else self.search( - [('code', '=', item['chuck_code'].replace("JKM", result['factory_short_name']))]).id, + [('code', '=', item['chuck_code'])]).id, "handle_id": False if not item['handle_code'] else self.search( - [('code', '=', item['handle_code'].replace("JKM", result['factory_short_name']))]).id, + [('code', '=', item['handle_code'])]).id, "suitable_machining_method_ids": [(6, 0, [])] if not item.get( 'suitable_machining_methods') else self.env['maintenance.equipment.image']._get_ids( item['suitable_machining_methods']), @@ -2302,7 +2306,7 @@ class Cutting_tool_standard_library(models.Model): if result['status'] == 1: for item in result['cutting_tool_standard_library_all_list']: cutting_tool_standard_library = self.search( - [("code", '=', item['code'].replace("JKM", result['factory_short_name'])), + [("code", '=', item['code']), ("active", 'in', [True, False])]) cutting_tool_type = self.env['sf.cutting.tool.type'].search( [("code", '=', item['cutting_tool_type_code'])]) @@ -2313,7 +2317,7 @@ class Cutting_tool_standard_library(models.Model): brand = self.env['sf.machine.brand'].search([("code", '=', item['brand_code'])]) if not cutting_tool_standard_library: self.create({ - "code": item['code'].replace("JKM", result['factory_short_name']), + "code": item['code'], "name": item['name'], "cutting_tool_material_id": cutting_tool_material.id, "cutting_tool_type_id": cutting_tool_type.id, @@ -2335,9 +2339,9 @@ class Cutting_tool_standard_library(models.Model): 'maintenance.equipment.image'].search( [('name', '=', item['fit_blade_shape'])]).id, "chuck_id": False if not item['chuck_code'] else self.search( - [('code', '=', item['chuck_code'].replace("JKM", result['factory_short_name']))]).id, + [('code', '=', item['chuck_code'])]).id, "handle_id": False if not item['handle_code'] else self.search( - [('code', '=', item['handle_code'].replace("JKM", result['factory_short_name']))]).id, + [('code', '=', item['handle_code'])]).id, "suitable_machining_method_ids": [(6, 0, [])] if not item.get( 'suitable_machining_method') else self.env['maintenance.equipment.image']._get_ids( item['suitable_machining_method']), @@ -2377,12 +2381,12 @@ class Cutting_tool_standard_library(models.Model): 'maintenance.equipment.image'].search( [('name', '=', item['fit_blade_shape'])]).id, "chuck_id": False if not item['chuck_code'] else self.search( - [('code', '=', item['chuck_code'].replace("JKM", result['factory_short_name']))]).id, + [('code', '=', item['chuck_code'])]).id, "handle_id": False if not item['handle_code'] else self.search( - [('code', '=', item['handle_code'].replace("JKM", result['factory_short_name']))]).id, + [('code', '=', item['handle_code'])]).id, "suitable_machining_method_ids": [(6, 0, [])] if not item.get( - 'suitable_machining_methods') else self.env['maintenance.equipment.image']._get_ids( - item['suitable_machining_methods']), + 'suitable_machining_method') else self.env['maintenance.equipment.image']._get_ids( + item['suitable_machining_method']), "blade_tip_characteristics_id": self.env['maintenance.equipment.image'].search( [('name', '=', item['blade_tip_characteristics'])]).id, "handle_type_id": self.env['maintenance.equipment.image'].search( @@ -2430,8 +2434,7 @@ class CuttingToolBasicParameters(models.Model): 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( [( 'code', '=', - integral_tool_item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + integral_tool_item['standard_library_code'])]).id, 'total_length': integral_tool_item['total_length'], 'blade_diameter': integral_tool_item['blade_diameter'], 'blade_length': integral_tool_item['blade_length'], @@ -2454,6 +2457,10 @@ class CuttingToolBasicParameters(models.Model): else: self.search([('code', '=', integral_tool_item['code'])]).write({ 'name': integral_tool_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [( + 'code', '=', + integral_tool_item['standard_library_code'])]).id, 'total_length': integral_tool_item['total_length'], 'blade_diameter': integral_tool_item['blade_diameter'], 'blade_length': integral_tool_item['blade_length'], @@ -2486,8 +2493,7 @@ class CuttingToolBasicParameters(models.Model): 'code': blade_item['code'], 'cutting_tool_type': '刀片', 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( - [('code', '=', blade_item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', blade_item['standard_library_code'])]).id, 'length': blade_item['length'], 'thickness': blade_item['thickness'], 'cutting_blade_length': blade_item['cutting_blade_length'], @@ -2516,6 +2522,8 @@ class CuttingToolBasicParameters(models.Model): else: self.search([('code', '=', blade_item['code'])]).write({ 'name': blade_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', blade_item['standard_library_code'])]).id, 'length': blade_item['length'], 'thickness': blade_item['thickness'], 'cutting_blade_length': blade_item['cutting_blade_length'], @@ -2554,8 +2562,7 @@ class CuttingToolBasicParameters(models.Model): 'code': chuck_item['code'], 'cutting_tool_type': '夹头', 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( - [('code', '=', chuck_item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', chuck_item['standard_library_code'])]).id, 'er_size_model': chuck_item['size_model'], 'min_clamping_diameter': chuck_item['clamping_diameter_min'], 'max_clamping_diameter': chuck_item['clamping_diameter_max'], @@ -2573,6 +2580,8 @@ class CuttingToolBasicParameters(models.Model): else: self.search([('code', '=', chuck_item['code'])]).write({ 'name': chuck_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', chuck_item['standard_library_code'])]).id, 'er_size_model': chuck_item['size_model'], 'min_clamping_diameter': chuck_item['clamping_diameter_min'], 'max_clamping_diameter': chuck_item['clamping_diameter_max'], @@ -2601,8 +2610,7 @@ class CuttingToolBasicParameters(models.Model): 'code': cutter_arbor_item['code'], 'cutting_tool_type': '刀杆', 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( - [('code', '=', cutter_arbor_item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', cutter_arbor_item['standard_library_code'])]).id, 'height': cutter_arbor_item['height'], 'width': cutter_arbor_item['width'], 'total_length': cutter_arbor_item['total_length'], @@ -2620,8 +2628,7 @@ class CuttingToolBasicParameters(models.Model): 'installing_structure': cutter_arbor_item['mounting_structure'], 'blade_id': False if not cutter_arbor_item['fit_blade_model_code'] else self.env[ 'sf.cutting_tool.standard.library'].search( - [('code', '=', cutter_arbor_item['fit_blade_model_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', cutter_arbor_item['fit_blade_model_code'])]).id, 'tool_shim': cutter_arbor_item['fit_knife_pad_model'], 'cotter_pin': cutter_arbor_item['fit_pin_model'], 'pressing_plate': cutter_arbor_item['fit_plate_model'], @@ -2632,6 +2639,8 @@ class CuttingToolBasicParameters(models.Model): else: self.search([('code', '=', cutter_arbor_item['code'])]).write({ 'name': cutter_arbor_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', cutter_arbor_item['standard_library_code'])]).id, 'height': cutter_arbor_item['height'], 'width': cutter_arbor_item['width'], 'total_length': cutter_arbor_item['total_length'], @@ -2649,8 +2658,7 @@ class CuttingToolBasicParameters(models.Model): 'installing_structure': cutter_arbor_item['mounting_structure'], 'blade_id': False if not cutter_arbor_item['fit_blade_model_code'] else self.env[ 'sf.cutting_tool.standard.library'].search( - [('code', '=', cutter_arbor_item['fit_blade_model_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', cutter_arbor_item['fit_blade_model_code'])]).id, 'tool_shim': cutter_arbor_item['fit_knife_pad_model'], 'cotter_pin': cutter_arbor_item['fit_pin_model'], 'pressing_plate': cutter_arbor_item['fit_plate_model'], @@ -2672,8 +2680,7 @@ class CuttingToolBasicParameters(models.Model): 'code': cutter_head_item['code'], 'cutting_tool_type': '刀盘', 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( - [('code', '=', cutter_head_item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', cutter_head_item['standard_library_code'])]).id, 'install_blade_tip_num': cutter_head_item['number_blade_installed'], 'blade_diameter': cutter_head_item['blade_diameter'], 'cutter_head_diameter': cutter_head_item['cutter_diameter'], @@ -2686,8 +2693,7 @@ class CuttingToolBasicParameters(models.Model): 'installing_structure': cutter_head_item['mounting_structure'], 'blade_id': False if not cutter_head_item['fit_blade_model_code'] else self.env[ 'sf.cutting_tool.standard.library'].search( - [('code', '=', cutter_head_item['fit_blade_model_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', cutter_head_item['fit_blade_model_code'])]).id, 'screw': cutter_head_item['fit_screw_model'], 'spanner': cutter_head_item['fit_wrench_model'], 'is_cooling_hole': cutter_head_item['is_cooling_hole'], @@ -2697,6 +2703,8 @@ class CuttingToolBasicParameters(models.Model): else: self.search([('code', '=', cutter_head_item['code'])]).write({ 'name': cutter_head_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', cutter_head_item['standard_library_code'])]).id, 'install_blade_tip_num': cutter_head_item['number_blade_installed'], 'blade_diameter': cutter_head_item['blade_diameter'], 'cutter_head_diameter': cutter_head_item['cutter_diameter'], @@ -2709,8 +2717,7 @@ class CuttingToolBasicParameters(models.Model): 'installing_structure': cutter_head_item['mounting_structure'], 'blade_id': False if not cutter_head_item['fit_blade_model_code'] else self.env[ 'sf.cutting_tool.standard.library'].search( - [('code', '=', cutter_head_item['fit_blade_model_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', cutter_head_item['fit_blade_model_code'])]).id, 'screw': cutter_head_item['fit_screw_model'], 'spanner': cutter_head_item['fit_wrench_model'], 'is_cooling_hole': cutter_head_item['is_cooling_hole'], @@ -2727,6 +2734,8 @@ class CuttingToolBasicParameters(models.Model): [('code', '=', knife_handle_item['code']), ('active', 'in', [True, False])]) val = { 'name': knife_handle_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', knife_handle_item['standard_library_code'])]).id, 'taper_shank_model': knife_handle_item['taper_shank_model'], 'total_length': knife_handle_item['total_length'], 'flange_shank_length': knife_handle_item['flange_length'], @@ -2751,9 +2760,6 @@ class CuttingToolBasicParameters(models.Model): if not knife_handle: val['code'] = knife_handle_item['code'] val['cutting_tool_type'] = '刀柄' - val['standard_library_id'] = self.env['sf.cutting_tool.standard.library'].search( - [('code', '=', knife_handle_item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id self.create(val) else: self.search([('code', '=', knife_handle_item['code'])]).write(val) @@ -2785,8 +2791,7 @@ class CuttingToolBasicParameters(models.Model): 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( [( 'code', '=', - integral_tool_item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + integral_tool_item['standard_library_code'])]).id, 'total_length': integral_tool_item['total_length'], 'blade_diameter': integral_tool_item['blade_diameter'], 'blade_length': integral_tool_item['blade_length'], @@ -2809,6 +2814,10 @@ class CuttingToolBasicParameters(models.Model): else: integral_tool.write({ 'name': integral_tool_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [( + 'code', '=', + integral_tool_item['standard_library_code'])]).id, 'total_length': integral_tool_item['total_length'], 'blade_diameter': integral_tool_item['blade_diameter'], 'blade_length': integral_tool_item['blade_length'], @@ -2841,8 +2850,7 @@ class CuttingToolBasicParameters(models.Model): 'code': blade_item['code'], 'cutting_tool_type': '刀片', 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( - [('code', '=', blade_item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', blade_item['standard_library_code'])]).id, 'length': blade_item['length'], 'thickness': blade_item['thickness'], 'cutting_blade_length': blade_item['cutting_blade_length'], @@ -2871,6 +2879,8 @@ class CuttingToolBasicParameters(models.Model): else: blade.write({ 'name': blade_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', blade_item['standard_library_code'])]).id, 'length': blade_item['length'], 'thickness': blade_item['thickness'], 'cutting_blade_length': blade_item['cutting_blade_length'], @@ -2909,8 +2919,7 @@ class CuttingToolBasicParameters(models.Model): 'code': chuck_item['code'], 'cutting_tool_type': '夹头', 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( - [('code', '=', chuck_item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', chuck_item['standard_library_code'])]).id, 'er_size_model': chuck_item['size_model'], 'min_clamping_diameter': chuck_item['clamping_diameter_min'], 'max_clamping_diameter': chuck_item['clamping_diameter_max'], @@ -2928,6 +2937,8 @@ class CuttingToolBasicParameters(models.Model): else: chuck.write({ 'name': chuck_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', chuck_item['standard_library_code'])]).id, 'er_size_model': chuck_item['size_model'], 'min_clamping_diameter': chuck_item['clamping_diameter_min'], 'max_clamping_diameter': chuck_item['clamping_diameter_max'], @@ -2956,8 +2967,7 @@ class CuttingToolBasicParameters(models.Model): 'code': cutter_arbor_item['code'], 'cutting_tool_type': '刀杆', 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( - [('code', '=', cutter_arbor_item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', cutter_arbor_item['standard_library_code'])]).id, 'height': cutter_arbor_item['height'], 'width': cutter_arbor_item['width'], 'total_length': cutter_arbor_item['total_length'], @@ -2975,8 +2985,7 @@ class CuttingToolBasicParameters(models.Model): 'installing_structure': cutter_arbor_item['mounting_structure'], 'blade_id': False if not cutter_arbor_item['fit_blade_model_code'] else self.env[ 'sf.cutting_tool.standard.library'].search( - [('code', '=', cutter_arbor_item['fit_blade_model_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', cutter_arbor_item['fit_blade_model_code'])]).id, 'tool_shim': cutter_arbor_item['fit_knife_pad_model'], 'cotter_pin': cutter_arbor_item['fit_pin_model'], 'pressing_plate': cutter_arbor_item['fit_plate_model'], @@ -2987,6 +2996,8 @@ class CuttingToolBasicParameters(models.Model): else: cutter_arbor.write({ 'name': cutter_arbor_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', cutter_arbor_item['standard_library_code'])]).id, 'height': cutter_arbor_item['height'], 'width': cutter_arbor_item['width'], 'total_length': cutter_arbor_item['total_length'], @@ -3006,8 +3017,7 @@ class CuttingToolBasicParameters(models.Model): self.env[ 'sf.cutting_tool.standard.library'].search( [('code', '=', - cutter_arbor_item['fit_blade_model_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + cutter_arbor_item['fit_blade_model_code'])]).id, 'tool_shim': cutter_arbor_item['fit_knife_pad_model'], 'cotter_pin': cutter_arbor_item['fit_pin_model'], 'pressing_plate': cutter_arbor_item['fit_plate_model'], @@ -3028,8 +3038,7 @@ class CuttingToolBasicParameters(models.Model): 'code': cutter_head_item['code'], 'cutting_tool_type': '刀盘', 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( - [('code', '=', cutter_head_item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', cutter_head_item['standard_library_code'])]).id, 'install_blade_tip_num': cutter_head_item['number_blade_installed'], 'blade_diameter': cutter_head_item['blade_diameter'], 'cutter_head_diameter': cutter_head_item['cutter_diameter'], @@ -3042,8 +3051,7 @@ class CuttingToolBasicParameters(models.Model): 'installing_structure': cutter_head_item['mounting_structure'], 'blade_id': False if not cutter_head_item['fit_blade_model_code'] else self.env[ 'sf.cutting_tool.standard.library'].search( - [('code', '=', cutter_head_item['fit_blade_model_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', cutter_head_item['fit_blade_model_code'])]).id, 'screw': cutter_head_item['fit_screw_model'], 'spanner': cutter_head_item['fit_wrench_model'], 'is_cooling_hole': cutter_head_item['is_cooling_hole'], @@ -3053,6 +3061,8 @@ class CuttingToolBasicParameters(models.Model): else: cutter_head.write({ 'name': cutter_head_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', cutter_head_item['standard_library_code'])]).id, 'install_blade_tip_num': cutter_head_item['number_blade_installed'], 'blade_diameter': cutter_head_item['blade_diameter'], 'cutter_head_diameter': cutter_head_item['cutter_diameter'], @@ -3066,8 +3076,7 @@ class CuttingToolBasicParameters(models.Model): 'blade_id': False if not cutter_head_item['fit_blade_model_code'] else self.env[ 'sf.cutting_tool.standard.library'].search( [('code', '=', - cutter_head_item['fit_blade_model_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + cutter_head_item['fit_blade_model_code'])]).id, 'screw': cutter_head_item['fit_screw_model'], 'spanner': cutter_head_item['fit_wrench_model'], 'is_cooling_hole': cutter_head_item['is_cooling_hole'], @@ -3088,8 +3097,7 @@ class CuttingToolBasicParameters(models.Model): 'code': knife_handle_item['code'], 'cutting_tool_type': '刀柄', 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( - [('code', '=', knife_handle_item['standard_library_code'].replace("JKM", result[ - 'factory_short_name']))]).id, + [('code', '=', knife_handle_item['standard_library_code'])]).id, 'total_length': knife_handle_item['total_length'], 'taper_shank_model': knife_handle_item['taper_shank_model'], 'flange_shank_length': knife_handle_item['flange_length'], @@ -3114,6 +3122,8 @@ class CuttingToolBasicParameters(models.Model): else: knife_handle.write({ 'name': knife_handle_item['name'], + 'standard_library_id': self.env['sf.cutting_tool.standard.library'].search( + [('code', '=', knife_handle_item['standard_library_code'])]).id, 'total_length': knife_handle_item['total_length'], 'taper_shank_model': knife_handle_item['taper_shank_model'], 'flange_shank_length': knife_handle_item['flange_length'], diff --git a/sf_quality/models/quality.py b/sf_quality/models/quality.py index 201103f1..ebe30a71 100644 --- a/sf_quality/models/quality.py +++ b/sf_quality/models/quality.py @@ -14,7 +14,8 @@ class QualityCheck(models.Model): ('waiting', '等待'), ('none', '待处理'), ('pass', '通过的'), - ('fail', '失败的')], string='状态', tracking=True, store=True, + ('fail', '失败的'), + ('cancel', '已取消'), ], string='状态', tracking=True, store=True, default='none', copy=False, compute='_compute_quality_state') individuation_page_PTD = fields.Boolean('个性化记录(是否显示后置三元检测[PTD]页签)', related='workorder_id.individuation_page_PTD') @@ -39,6 +40,14 @@ class QualityCheck(models.Model): operation_id = fields.Many2one('mrp.routing.workcenter', '作业', store=True, compute='_compute_operation_id') is_inspect = fields.Boolean('需送检', related='point_id.is_inspect') + lot_name = fields.Char('批次/序列号 名称', compute='_compute_lot_name', store=True) + + @api.depends('move_line_id', 'move_line_id.lot_name') + def _compute_lot_name(self): + for qc in self: + if qc.move_line_id: + qc.lot_name = qc.move_line_id.lot_name + @api.depends('point_id.operation_id') def _compute_operation_id(self): for qc in self: @@ -84,6 +93,17 @@ class QualityCheck(models.Model): raise ValidationError('请填写【判定结果】里的信息') if self.test_results == '合格': raise ValidationError('请重新选择【判定结果】-【检测结果】') + if self.workorder_id.routing_type != 'CNC加工' and self.workorder_id.individuation_page_PTD is False: + self.workorder_id.production_id.write({'detection_result_ids': [(0, 0, { + 'rework_reason': self.reason, + 'detailed_reason': self.detailed_reason, + 'processing_panel': self.workorder_id.processing_panel, + 'routing_type': self.workorder_id.routing_type, + 'handle_result': '待处理', + 'test_results': self.test_results, + 'test_report': self.workorder_id.detection_report})], + 'is_scrap': True if self.test_results == '报废' else False + }) if self.workorder_id.state not in ['done']: self.workorder_id.write( {'test_results': self.test_results, 'reason': self.reason, 'detailed_reason': self.detailed_reason}) @@ -110,4 +130,3 @@ class QualityCheck(models.Model): return "零件特采发送成功" else: raise ValidationError("零件特采发送失败") - diff --git a/sf_quality/views/quality_check_view.xml b/sf_quality/views/quality_check_view.xml index 61309737..00b139de 100644 --- a/sf_quality/views/quality_check_view.xml +++ b/sf_quality/views/quality_check_view.xml @@ -74,6 +74,7 @@ + @@ -89,8 +90,15 @@ - + + { + 'is_web_request': True, + 'search_default_progress': 1, + 'search_default_passed': 1, + 'search_default_failed': 1, + } + \ No newline at end of file diff --git a/sf_sale/__manifest__.py b/sf_sale/__manifest__.py index e824240e..c12f94fd 100644 --- a/sf_sale/__manifest__.py +++ b/sf_sale/__manifest__.py @@ -28,6 +28,7 @@ 'web.assets_backend': [ 'sf_sale/static/js/setTableWidth.js', 'sf_sale/static/src/css/purchase_list.css', + 'sf_sale/static/lib/*', ] }, 'demo': [ diff --git a/sf_sale/models/sale_order.py b/sf_sale/models/sale_order.py index 31681932..b719ffd1 100644 --- a/sf_sale/models/sale_order.py +++ b/sf_sale/models/sale_order.py @@ -132,7 +132,7 @@ class ReSaleOrder(models.Model): 'name': '%s/%s/%s/%s/%s/%s' % ( self.format_float(product.model_long), self.format_float(product.model_width), - self.format_float(product.model_height), + self.format_float(product.model_height), self.format_float(product.model_volume), machining_accuracy_name, product.materials_id.name), @@ -401,12 +401,6 @@ class RePurchaseOrder(models.Model): def button_confirm(self): result = super(RePurchaseOrder, self).button_confirm() for item in self: - # 确认订单时,自动分配序列号 - if item.picking_ids: - for picking_id in item.picking_ids: - if picking_id.move_ids: - for move_id in picking_id.move_ids: - move_id.put_move_line() for line in item.order_line: if line.product_id.categ_type == '表面工艺': if item.origin: @@ -517,9 +511,10 @@ class ResUserToSale(models.Model): @api.model def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): + domain = [] if self._context.get('is_sale'): if self.env.user.has_group('sf_base.group_sale_director'): - domain = [] + pass elif self.env.user.has_group('sf_base.group_sale_salemanager'): if self.id != self.env.user.id: domain = [('id', '=', self.id)] @@ -528,7 +523,7 @@ class ResUserToSale(models.Model): return self._search(domain, limit=limit, access_rights_uid=name_get_uid) elif self._context.get('supplier_rank'): if self.env.user.has_group('sf_base.group_purchase_director'): - domain = [] + pass elif self.env.user.has_group('sf_base.group_purchase'): if self.id != self.env.user.id: domain = [('id', '=', self.id)] diff --git a/sf_sale/static/lib/merge_field.js b/sf_sale/static/lib/merge_field.js new file mode 100644 index 00000000..6278c36e --- /dev/null +++ b/sf_sale/static/lib/merge_field.js @@ -0,0 +1,18 @@ +/** @odoo-module */ + +import { Component } from "@odoo/owl"; +import { registry } from "@web/core/registry"; + + +export class MergeField extends Component { + get mergeValue() { + const data = this.props.record.data; + + const v = data?.product_uom_qty + const unit = data?.product_uom[1] + return `${v} ${unit}` + } +} +MergeField.template = "jikimo_sf.MergeField"; + +registry.category("fields").add("merge_field", MergeField); diff --git a/sf_sale/static/lib/merge_field.xml b/sf_sale/static/lib/merge_field.xml new file mode 100644 index 00000000..59103fa9 --- /dev/null +++ b/sf_sale/static/lib/merge_field.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/sf_sale/static/src/css/purchase_list.css b/sf_sale/static/src/css/purchase_list.css index 2deacb9c..17df6065 100644 --- a/sf_sale/static/src/css/purchase_list.css +++ b/sf_sale/static/src/css/purchase_list.css @@ -1,3 +1,11 @@ .purchase_order_list_name { min-width: 62px !important; +} + +.o_list_renderer .o_list_table .o_data_row td.o_data_cell.o_field_cell.o_list_char.section_and_note_text, .section_and_note_text span{ + white-space: wrap!important; + overflow: auto!important; + text-overflow: unset!important; + word-wrap: break-word; + word-break: break-all; } \ 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 ecc061fa..209fd4fa 100644 --- a/sf_sale/views/purchase_order_view.xml +++ b/sf_sale/views/purchase_order_view.xml @@ -139,7 +139,7 @@ - + {'readonly': [('state', 'in', ['purchase'])]} diff --git a/sf_sale/views/sale_order_view.xml b/sf_sale/views/sale_order_view.xml index 1c96bf59..5a6b08de 100644 --- a/sf_sale/views/sale_order_view.xml +++ b/sf_sale/views/sale_order_view.xml @@ -102,7 +102,7 @@ + string="模型文件" attrs="{'readonly': [('state', 'in', ['draft'])], 'isInList': True}"/> @@ -112,6 +112,7 @@ {'no_create': True} {'is_sale_order_line': True } + section_and_note_text {'readonly': [('state', 'in', ['cancel','sale'])]} @@ -122,6 +123,16 @@ + + + + + + hide + + + hide + {'readonly': [('state', 'in', ['cancel','sale'])]} @@ -228,6 +239,18 @@ + + sale.order.line.tree.sale.stock.qty.sf + + sale.order + + + + + + + + sale.order.quotation.tree.inherit.sf sale.order diff --git a/sf_stock/models/stock_picking.py b/sf_stock/models/stock_picking.py index f8105097..7b73485b 100644 --- a/sf_stock/models/stock_picking.py +++ b/sf_stock/models/stock_picking.py @@ -18,7 +18,7 @@ class StockPicking(models.Model): @api.depends('name') def _compute_pro_purchase_count(self): for sp in self: - if sp: + if sp.name and sp.name != '/': po_ids = self.env['purchase.order'].sudo().search([ ('origin', 'like', sp.name), ('purchase_type', '=', 'standard')]) if po_ids: @@ -52,7 +52,7 @@ class StockPicking(models.Model): @api.depends('name') def _compute_pro_out_purchase_count(self): for sp in self: - if sp: + if sp.name and sp.name != '/': po_ids = self.env['purchase.order'].sudo().search([ ('origin', 'like', sp.name), ('purchase_type', '=', 'outsourcing')]) if po_ids: diff --git a/sf_warehouse/models/model.py b/sf_warehouse/models/model.py index fb80618a..848b1f66 100644 --- a/sf_warehouse/models/model.py +++ b/sf_warehouse/models/model.py @@ -935,11 +935,28 @@ class SfStockPicking(models.Model): _inherit = 'stock.picking' check_in = fields.Char(string='查询是否为入库单', compute='_check_is_in') + product_uom_qty_sp = fields.Float('需求数量', compute='_compute_product_uom_qty_sp', store=True) + + @api.depends('move_ids_without_package', 'move_ids_without_package.product_uom_qty') + def _compute_product_uom_qty_sp(self): + for sp in self: + if sp.move_ids_without_package: + sp.product_uom_qty_sp = 0 + for move_id in sp.move_ids_without_package: + sp.product_uom_qty_sp += move_id.product_uom_qty + else: + sp.product_uom_qty_sp = 0 def batch_stock_move(self): """ 批量调拨,非就绪状态的会被忽略,完成后有通知提示 """ + # 对所以调拨单的质检单进行是否完成校验 + sp_ids = [sp.id for sp in self] + qc_ids = self.env['quality.check'].sudo().search( + [('picking_id', 'in', sp_ids), ('quality_state', 'in', ['waiting', 'none'])]) + if qc_ids: + raise ValidationError(f'单据{list(set(qc.picking_id.name for qc in qc_ids))}未完成质量检查,完成后再试。') for record in self: if record.state != 'assigned': continue diff --git a/sf_warehouse/views/change_stock_move_views.xml b/sf_warehouse/views/change_stock_move_views.xml index 9e1073c9..641a4ec2 100644 --- a/sf_warehouse/views/change_stock_move_views.xml +++ b/sf_warehouse/views/change_stock_move_views.xml @@ -79,6 +79,9 @@ + + True +