Merge branch 'release/release_2.7'

This commit is contained in:
胡尧
2025-01-07 20:16:29 +08:00
103 changed files with 2932 additions and 667 deletions

View File

@@ -139,6 +139,7 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', {
owl.onMounted(() => { owl.onMounted(() => {
this.activeElement = this.uiService.activeElement; this.activeElement = this.uiService.activeElement;
this.setRequired() this.setRequired()
this.listherHeaderBodyNum()
}) })
return this._super(...arguments); return this._super(...arguments);
}, },
@@ -165,6 +166,26 @@ patch(ListRenderer.prototype, 'jikimo_frontend.ListRenderer', {
} catch (e) { } catch (e) {
console.log(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)
}
} }
}) })

View File

@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
from . import controllers
from . import models
from . import wizards

View File

@@ -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',
],
},
}

View File

@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import controllers

View File

@@ -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/<model("jikimo_purchase_tier_validation.jikimo_purchase_tier_validation"):obj>', auth='public')
# def object(self, obj, **kw):
# return http.request.render('jikimo_purchase_tier_validation.object', {
# 'object': obj
# })

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- 创建采购合同文件夹 -->
<record id="documents_purchase_contracts_folder" model="documents.folder">
<field name="name">采购合同</field>
<field name="description">存放采购合同相关文件</field>
<field name="sequence">10</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,30 @@
<odoo>
<data>
<!--
<record id="object0" model="jikimo_purchase_tier_validation.jikimo_purchase_tier_validation">
<field name="name">Object 0</field>
<field name="value">0</field>
</record>
<record id="object1" model="jikimo_purchase_tier_validation.jikimo_purchase_tier_validation">
<field name="name">Object 1</field>
<field name="value">10</field>
</record>
<record id="object2" model="jikimo_purchase_tier_validation.jikimo_purchase_tier_validation">
<field name="name">Object 2</field>
<field name="value">20</field>
</record>
<record id="object3" model="jikimo_purchase_tier_validation.jikimo_purchase_tier_validation">
<field name="name">Object 3</field>
<field name="value">30</field>
</record>
<record id="object4" model="jikimo_purchase_tier_validation.jikimo_purchase_tier_validation">
<field name="name">Object 4</field>
<field name="value">40</field>
</record>
-->
</data>
</odoo>

View File

@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
from . import models

View File

@@ -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"<strong>{current_user}</strong> 提交审批",
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 = "发票账单"

View File

@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_ir_attachment_wizard ir.attachment.wizard model_ir_attachment_wizard base.group_user 1 1 1 1

View File

@@ -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"];
},
},
},
});

View File

@@ -0,0 +1,24 @@
<odoo>
<data>
<!--
<template id="listing">
<ul>
<li t-foreach="objects" t-as="object">
<a t-attf-href="#{ root }/objects/#{ object.id }">
<t t-esc="object.display_name"/>
</a>
</li>
</ul>
</template>
<template id="object">
<h1><t t-esc="object.display_name"/></h1>
<dl>
<t t-foreach="object._fields" t-as="field">
<dt><t t-esc="field"/></dt>
<dd><t t-esc="object[field]"/></dd>
</t>
</dl>
</template>
-->
</data>
</odoo>

View File

@@ -0,0 +1,98 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record model="ir.ui.view" id="tier_validation_view_approved_purchase_order_form_inherit">
<field name="name">tier_validation_view_approved_purchase_order_form_inherit</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase_order_approved.purchase_order_form"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='button_release']" position="replace">
</xpath>
</field>
</record>
<record model="ir.ui.view" id="tier_validation_view_purchase_order_form_inherit">
<field name="name">tier_validation_view_purchase_order_form_inherit</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_form"/>
<field name="arch" type="xml">
<xpath expr="//header/button[@name='button_approve']" position="replace">
</xpath>
<xpath expr="//header/button[@name='button_cancel']" position="replace">
</xpath>
<xpath expr="//header/field[@name='state']" position="replace">
<field name="state" widget="statusbar" statusbar_visible="draft,sent,to approve, approved, purchase" readonly="1"/>
</xpath>
<xpath expr="//header/button[last()]" position="after">
<button name="button_cancel" states="draft,to approve,sent,purchase" string="取消" type="object" data-hotkey="x" />
</xpath>
<xpath expr="//header/button[@name='action_rfq_send'][1]" position="before">
<field name="validation_status" invisible="1"/>
<field name="is_upload_contract_file" invisible="1"/>
<button name="upload_contract_file" string="上传合同" type="object" class="oe_highlight" attrs="{'invisible': ['|', '|', ('validation_status', '!=', 'no'), ('is_upload_contract_file', '=', True), ('state', 'not in', ['draft', 'sent'])]}"/>]}"/>
<button name="delete_contract_file" string="删除合同" type="object" class="oe_highlight" attrs="{'invisible': ['|', ('validation_status', '!=', 'no'), ('is_upload_contract_file', '=', False)]}"/>
</xpath>
<xpath expr="//notebook/page[1]" position="before">
<page string="合同" name="contract_documents"
attrs="{'invisible': [('contract_document_id', '=', False)]}"
autofocus="autofocus">
<group>
<group>
<field name="contract_document_id" invisible="1"/>
<field name="contract_file_name" invisible="1"/>
<field name="contract_file"
widget="adaptive_viewer"
filename="contract_file_name"/>
</group>
</group>
</page>
</xpath>
</field>
</record>
<!-- actions opening views on models -->
<!--
<record model="ir.actions.act_window" id="jikimo_purchase_tier_validation.action_window">
<field name="name">jikimo_purchase_tier_validation window</field>
<field name="res_model">jikimo_purchase_tier_validation.jikimo_purchase_tier_validation</field>
<field name="view_mode">tree,form</field>
</record>
-->
<!-- server action to the one above -->
<!--
<record model="ir.actions.server" id="jikimo_purchase_tier_validation.action_server">
<field name="name">jikimo_purchase_tier_validation server</field>
<field name="model_id" ref="model_jikimo_purchase_tier_validation_jikimo_purchase_tier_validation"/>
<field name="state">code</field>
<field name="code">
action = {
"type": "ir.actions.act_window",
"view_mode": "tree,form",
"res_model": model._name,
}
</field>
</record>
-->
<!-- Top menu item -->
<!--
<menuitem name="jikimo_purchase_tier_validation" id="jikimo_purchase_tier_validation.menu_root"/>
-->
<!-- menu categories -->
<!--
<menuitem name="Menu 1" id="jikimo_purchase_tier_validation.menu_1" parent="jikimo_purchase_tier_validation.menu_root"/>
<menuitem name="Menu 2" id="jikimo_purchase_tier_validation.menu_2" parent="jikimo_purchase_tier_validation.menu_root"/>
-->
<!-- actions -->
<!--
<menuitem name="List" id="jikimo_purchase_tier_validation.menu_1_list" parent="jikimo_purchase_tier_validation.menu_1"
action="jikimo_purchase_tier_validation.action_window"/>
<menuitem name="Server to list" id="jikimo_purchase_tier_validation" parent="jikimo_purchase_tier_validation.menu_2"
action="jikimo_purchase_tier_validation.action_server"/>
-->
</data>
</odoo>

View File

@@ -0,0 +1,2 @@
from . import upload_file_wizard
from . import comment_wizard

View File

@@ -0,0 +1,15 @@
from odoo import models, fields
class CommentWizard(models.TransientModel):
_inherit = "comment.wizard"
def add_comment(self):
rec = self.env[self.res_model].browse(self.res_id)
self.review_ids = rec.review_ids
result = super(CommentWizard, self).add_comment()
return result

View File

@@ -0,0 +1,114 @@
from odoo import models, fields, api, _
class IrAttachmentWizard(models.TransientModel):
_name = 'ir.attachment.wizard'
_description = '文件上传向导'
attachment = fields.Binary(string='选择文件', required=True)
filename = fields.Char(string='文件名')
res_model = fields.Char()
res_id = fields.Integer()
# def action_upload_file(self):
# self.ensure_one()
# # 首先创建 ir.attachment
# attachment = self.env['ir.attachment'].create({
# 'name': self.filename,
# 'type': 'binary',
# 'datas': self.attachment,
# 'res_model': self.res_model,
# 'res_id': self.res_id,
# })
#
# # 获取默认的文档文件夹
# workspace = self.env['documents.folder'].search([('name', '=', '采购合同')], limit=1)
#
# # 创建 documents.document 记录
# document = self.env['documents.document'].create({
# 'name': self.filename,
# 'attachment_id': attachment.id,
# 'folder_id': workspace.id,
# 'res_model': self.res_model,
# 'res_id': self.res_id,
# })
#
# return {
# 'type': 'ir.actions.client',
# 'tag': 'display_notification',
# 'params': {
# 'title': _('成功'),
# 'message': _('文件上传成功'),
# 'type': 'success',
# }
# }
def action_upload_file(self):
self.ensure_one()
# 获取当前用户的 partner_id
current_partner = self.env.user.partner_id
# 首先创建 ir.attachment
attachment = self.env['ir.attachment'].create({
'name': self.filename,
'type': 'binary',
'datas': self.attachment,
'res_model': self.res_model,
'res_id': self.res_id,
})
# 获取默认的文档文件夹
workspace = self.env['documents.folder'].search([('name', '=', '采购合同')], limit=1)
# 创建 documents.document 记录
document = self.env['documents.document'].create({
'name': self.filename,
'attachment_id': attachment.id,
'folder_id': workspace.id,
'res_model': self.res_model,
'res_id': self.res_id,
'partner_id': current_partner.id,
})
# 更新采购订单的合同文档字段
purchase_order = self.env['purchase.order'].browse(self.res_id)
purchase_order.write({
'contract_document_id': document.id,
'is_upload_contract_file': True
})
# 显示成功消息并关闭向导
message = {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('成功'),
'message': _('文件上传成功'),
'type': 'success',
'sticky': False, # 自动消失
'next': {
'type': 'ir.actions.act_window_close'
}
}
}
return message
# def action_upload_file(self):
# self.ensure_one()
# attachment = self.env['ir.attachment'].create({
# 'name': self.filename,
# 'type': 'binary',
# 'datas': self.attachment,
# 'res_model': self.res_model,
# 'res_id': self.res_id,
# })
# return {
# 'type': 'ir.actions.client',
# 'tag': 'display_notification',
# 'params': {
# 'title': _('成功'),
# 'message': _('文件上传成功'),
# 'type': 'success',
# }
# }

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_upload_file_wizard_form" model="ir.ui.view">
<field name="name">ir.attachment.wizard.form</field>
<field name="model">ir.attachment.wizard</field>
<field name="arch" type="xml">
<form string="上传文件">
<group>
<field name="attachment" widget="binary" filename="filename" options="{'accepted_file_extensions': '.pdf,.doc,.docx,.jpg,.jpeg,.png'}"/>
<field name="filename" invisible="1"/>
<field name="res_model" invisible="1"/>
<field name="res_id" invisible="1"/>
</group>
<footer>
<button name="action_upload_file" string="确认上传" type="object" class="btn-primary"/>
<button string="取消" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@@ -23,4 +23,4 @@ class ProductTemplate(models.Model):
self.is_bfm = product_template_id.is_bfm self.is_bfm = product_template_id.is_bfm
self.is_manual_processing = product_template_id.is_manual_processing self.is_manual_processing = product_template_id.is_manual_processing
# 复制 seller_ids # 复制 seller_ids
self.seller_ids = [(0, 0, {'partner_id': seller.partner_id.id, 'delay': 1.0}) for seller in product_template_id.seller_ids] self.seller_ids = [(0, 0, {'partner_id': seller.partner_id.id, 'delay': 1.0, 'price': seller.price}) for seller in product_template_id.seller_ids]

View File

@@ -51,11 +51,11 @@ class JikimoWorkorderException(models.Model):
return res return res
def _get_message(self, message_queue_ids): def _get_message(self, message_queue_ids):
contents = super(JikimoWorkorderException, self)._get_message(message_queue_ids) contents, _ = super(JikimoWorkorderException, self)._get_message(message_queue_ids)
url = self.env['ir.config_parameter'].get_param('web.base.url') url = self.env['ir.config_parameter'].get_param('web.base.url')
action_id = self.env.ref('mrp.mrp_production_action').id action_id = self.env.ref('mrp.mrp_production_action').id
for index, content in enumerate(contents): for index, content in enumerate(contents):
exception_id = self.env['jikimo.workorder.exception'].browse(message_queue_ids[index].res_id) exception_id = self.env['jikimo.workorder.exception'].browse(message_queue_ids[index].res_id)
url = url + '/web#id=%s&view_type=form&action=%s' % (exception_id.workorder_id.production_id.id, action_id) url = url + '/web#id=%s&view_type=form&action=%s' % (exception_id.workorder_id.production_id.id, action_id)
contents[index] = content.replace('{{url}}', url) contents[index] = content.replace('{{url}}', url)
return contents return contents, message_queue_ids

View File

@@ -138,7 +138,7 @@ if env.user.has_group('mrp.group_mrp_workorder_dependencies'):
<button name="openMenuPopup" t-att-disabled="isBlocked" class="btn btn-secondary o_workorder_icon_btn fa fa-bars" type="workorder_event" title="menu"/> <button name="openMenuPopup" t-att-disabled="isBlocked" class="btn btn-secondary o_workorder_icon_btn fa fa-bars" type="workorder_event" title="menu"/>
<span groups="mrp_workorder.group_mrp_wo_tablet_timer"> <span groups="mrp_workorder.group_mrp_wo_tablet_timer">
<button name="button_pending" type="object" class="btn btn-secondary" attrs="{'invisible': ['|', ('is_user_working', '=', False), ('working_state', '=', 'blocked')]}" barcode_trigger="pause" string="PAUSE"/> <button name="button_pending" type="object" class="btn btn-secondary" attrs="{'invisible': ['|', ('is_user_working', '=', False), ('working_state', '=', 'blocked')]}" barcode_trigger="pause" string="PAUSE"/>
<button name="button_start" type="object" class="btn btn-warning" attrs="{'invisible': ['|', '|', ('is_user_working', '=', True), ('working_state', '=', 'blocked'), ('state', '=', ('done', 'cancel'))]}" barcode_trigger="pause" string="CONTINUE"/> <button name="button_start" type="object" class="btn btn-warning" attrs="{'invisible': ['|', '|', ('is_user_working', '=', True), ('working_state', '=', 'blocked'), ('state', '=', ('done','rework', 'cancel'))]}" barcode_trigger="pause" string="CONTINUE"/>
<button name="button_unblock" type="object" class="btn btn-secondary btn-danger o_unblock" attrs="{'invisible': [('working_state', '!=', 'blocked')]}">Unblock</button> <button name="button_unblock" type="object" class="btn btn-secondary btn-danger o_unblock" attrs="{'invisible': [('working_state', '!=', 'blocked')]}">Unblock</button>
<field name="duration" widget="mrp_timer" class="ms-1" readonly="1"/> <field name="duration" widget="mrp_timer" class="ms-1" readonly="1"/>
</span> </span>

View File

@@ -8,7 +8,7 @@
'sequence': 120, 'sequence': 120,
'summary': 'Control the quality of your products', 'summary': 'Control the quality of your products',
'website': 'https://www.odoo.com/app/quality', 'website': 'https://www.odoo.com/app/quality',
'depends': ['quality'], 'depends': ['quality', 'sf_manufacturing'],
'description': """ 'description': """
Quality Control Quality Control
=============== ===============

View File

@@ -7,6 +7,7 @@ from datetime import datetime
import random import random
from odoo import api, models, fields, _ from odoo import api, models, fields, _
from odoo.api import depends
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_round from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT, float_round
from odoo.osv.expression import OR from odoo.osv.expression import OR
@@ -122,7 +123,13 @@ class QualityPoint(models.Model):
class QualityCheck(models.Model): class QualityCheck(models.Model):
_inherit = "quality.check" _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) failure_message = fields.Html(related='point_id.failure_message', readonly=True)
measure = fields.Float('Measure', default=0.0, digits='Quality Tests', tracking=True) measure = fields.Float('Measure', default=0.0, digits='Quality Tests', tracking=True)
measure_success = fields.Selection([ 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') 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") testing_percentage_within_lot = fields.Float(related="point_id.testing_percentage_within_lot")
product_tracking = fields.Selection(related='product_id.tracking') 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') @api.depends('measure_success')
def _compute_warning_message(self): def _compute_warning_message(self):
@@ -294,6 +329,19 @@ class QualityAlert(models.Model):
_inherit = "quality.alert" _inherit = "quality.alert"
title = fields.Char('Title') 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): def action_see_check(self):
return { return {

View File

@@ -90,6 +90,8 @@
<field name="lot_id" context="{'default_product_id': product_id}" <field name="lot_id" context="{'default_product_id': product_id}"
groups="stock.group_production_lot"/> groups="stock.group_production_lot"/>
<field name="picking_id"/> <field name="picking_id"/>
<field name="part_name"/>
<field name="part_number"/>
</group> </group>
<group> <group>
<field name="team_id"/> <field name="team_id"/>
@@ -150,6 +152,10 @@
<field name="date_assign" position="after"> <field name="date_assign" position="after">
<field name="company_id" groups="base.main_company"/> <field name="company_id" groups="base.main_company"/>
</field> </field>
<field name="product_tmpl_id" position="after">
<field name="part_name" optional="show"/>
<field name="part_number" optional="show"/>
</field>
</field> </field>
</record> </record>
@@ -260,6 +266,8 @@
<field name="company_id" invisible="1"/> <field name="company_id" invisible="1"/>
<field name="product_id" attrs="{'invisible' : [('measure_on', '=', 'operation')]}"/> <field name="product_id" attrs="{'invisible' : [('measure_on', '=', 'operation')]}"/>
<field name="measure_on" attrs="{'readonly': [('point_id', '!=', False)]}"/> <field name="measure_on" attrs="{'readonly': [('point_id', '!=', False)]}"/>
<field name="part_name"/>
<field name="part_number"/>
<field name="show_lot_text" invisible="1"/> <field name="show_lot_text" invisible="1"/>
<field name="move_line_id" invisible="1"/> <field name="move_line_id" invisible="1"/>
<field name="product_tracking" invisible="1"/> <field name="product_tracking" invisible="1"/>
@@ -389,6 +397,8 @@
<field name="name" decoration-bf="1"/> <field name="name" decoration-bf="1"/>
<field name="measure_on" optional="show"/> <field name="measure_on" optional="show"/>
<field name='product_id' optional="show"/> <field name='product_id' optional="show"/>
<field name="part_name" optional="hide"/>
<field name='part_number' optional="show"/>
<field name="lot_id" invisible="context.get('show_lots_text')"/> <field name="lot_id" invisible="context.get('show_lots_text')"/>
<field name="lot_name" invisible="not context.get('show_lots_text')"/> <field name="lot_name" invisible="not context.get('show_lots_text')"/>
<field name="picking_id" optional="hide" string="Transfer"/> <field name="picking_id" optional="hide" string="Transfer"/>
@@ -446,6 +456,10 @@
<filter string="Control Point" name="groupby_point_id" context="{'group_by': 'point_id'}"/> <filter string="Control Point" name="groupby_point_id" context="{'group_by': 'point_id'}"/>
<filter string="Team" name="groupby_team_id" context="{'group_by': 'team_id'}"/> <filter string="Team" name="groupby_team_id" context="{'group_by': 'team_id'}"/>
</group> </group>
<searchpanel>
<field name="quality_check_type" icon="fa-filter" enable_counters="1"/>
<field name="quality_state" icon="fa-filter" enable_counters="1"/>
</searchpanel>
</search> </search>
</field> </field>
</record> </record>
@@ -505,6 +519,7 @@
<field name="name">Quality Checks</field> <field name="name">Quality Checks</field>
<field name="res_model">quality.check</field> <field name="res_model">quality.check</field>
<field name="view_mode">tree,kanban,form,pivot,graph</field> <field name="view_mode">tree,kanban,form,pivot,graph</field>
<field name="context">{'is_web_request': True}</field>
<field name="help" type="html"> <field name="help" type="html">
<p class="o_view_nocontent_smiling_face"> <p class="o_view_nocontent_smiling_face">
No quality check found No quality check found

View File

@@ -151,6 +151,12 @@ class JdEclp(models.Model):
_logger.info('准备调接口1') _logger.info('准备调接口1')
url1 = config['bfm_url_new'] + '/api/create/jd/order' url1 = config['bfm_url_new'] + '/api/create/jd/order'
requests.post(url1, json=json1, data=None) 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('调用成功1')
_logger.info('准备调接口2') _logger.info('准备调接口2')
json2 = { json2 = {

View File

@@ -36,6 +36,8 @@ class StatusChange(models.Model):
# 使用super()来调用原始方法(在本例中为'sale.order'模型的'action_confirm'方法) # 使用super()来调用原始方法(在本例中为'sale.order'模型的'action_confirm'方法)
try: try:
res = super(StatusChange, self).action_confirm() res = super(StatusChange, self).action_confirm()
# 修改销售订单状态为【加工中】
self.write({'state': 'processing'})
logging.info('原生方法返回结果:%s' % res) logging.info('原生方法返回结果:%s' % res)
# 原有方法执行后进行额外的操作如调用外部API # 原有方法执行后进行额外的操作如调用外部API
process_start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') process_start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

View File

@@ -9,9 +9,9 @@ class StockRuleInherit(models.Model):
@api.model @api.model
def _run_buy(self, procurements): def _run_buy(self, procurements):
# 判断补货组的采购类型 # 判断补货组的采购类型
procurements_group = {'standard': [], 'consignment': []} procurements_group = {'standard': [], 'outsourcing': []}
for procurement, rule in procurements: for procurement, rule in procurements:
is_consignment = False is_outsourcing = False
product = procurement.product_id product = procurement.product_id
# 获取主 BOM # 获取主 BOM
bom = self.env['mrp.bom'].search([('product_tmpl_id', '=', product.product_tmpl_id.id)], limit=1) 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: for route in raw_material.route_ids:
# print('route.name:', route.name) # print('route.name:', route.name)
if route.name == '按订单补给外包商': if route.name == '按订单补给外包商':
is_consignment = True is_outsourcing = True
if is_consignment: if is_outsourcing:
procurements_group['consignment'].append((procurement, rule)) procurements_group['outsourcing'].append((procurement, rule))
else: else:
procurements_group['standard'].append((procurement, rule)) procurements_group['standard'].append((procurement, rule))
for key, value in procurements_group.items(): for key, value in procurements_group.items():
super(StockRuleInherit, self)._run_buy(value) super(StockRuleInherit, self)._run_buy(value)
if key == 'consignment': if key == 'outsourcing':
for procurement, rule in value: for procurement, rule in value:
supplier = procurement.values.get('supplier') supplier = procurement.values.get('supplier')
if supplier: if supplier:
@@ -49,7 +49,7 @@ class StockRuleInherit(models.Model):
], limit=1) ], limit=1)
logging.info("po=: %s", po) logging.info("po=: %s", po)
if po: if po:
po.write({'purchase_type': 'consignment'}) po.write({'purchase_type': 'outsourcing'})
# # 首先调用父类的 _run_buy 方法,以保留原有逻辑 # # 首先调用父类的 _run_buy 方法,以保留原有逻辑
# super(StockRuleInherit, self)._run_buy(procurements) # super(StockRuleInherit, self)._run_buy(procurements)
@@ -83,5 +83,5 @@ class StockRuleInherit(models.Model):
# ], limit=1) # ], limit=1)
# logging.info("po=: %s", po) # logging.info("po=: %s", po)
# if po: # if po:
# po.write({'purchase_type': 'consignment'}) # po.write({'purchase_type': 'outsourcing'})
# break # break

View File

@@ -23,6 +23,16 @@ class JkmPracticeEmployee(models.Model):
vals["we_id"] = self._get_we_id(vals.get('work_email')) vals["we_id"] = self._get_we_id(vals.get('work_email'))
return super(JkmPracticeEmployee, self).write(vals) 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') @api.depends('work_contact_id', 'work_contact_id.mobile', 'work_contact_id.email')
def _compute_work_contact_details(self): def _compute_work_contact_details(self):
for employee in self: for employee in self:

View File

@@ -25,7 +25,7 @@
# 只有它被屏蔽了 # 只有它被屏蔽了
# 'views/SfWorkOrderBarcodes.xml', # 'views/SfWorkOrderBarcodes.xml',
'views/WorkCenterBarcodes.xml', 'views/WorkCenterBarcodes.xml',
'views/Stock_picking_Barcodes.xml', # 'views/Stock_picking_Barcodes.xml',
'views/machine_monitor.xml', 'views/machine_monitor.xml',
'views/machine_info_present.xml', 'views/machine_info_present.xml',
'views/delivery_record.xml', 'views/delivery_record.xml',

View File

@@ -24,6 +24,7 @@
'wizard/production_wizard_views.xml', 'wizard/production_wizard_views.xml',
'wizard/production_technology_wizard_views.xml', 'wizard/production_technology_wizard_views.xml',
'wizard/production_technology_re_adjust_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/mrp_views_menus.xml',
'views/agv_scheduling_views.xml', 'views/agv_scheduling_views.xml',
'views/stock_lot_views.xml', 'views/stock_lot_views.xml',
@@ -38,6 +39,8 @@
'views/sf_maintenance_equipment.xml', 'views/sf_maintenance_equipment.xml',
'views/res_config_settings_views.xml', 'views/res_config_settings_views.xml',
'views/sale_order_views.xml', 'views/sale_order_views.xml',
'views/mrp_workorder_batch_replan.xml',
'views/purchase_order_view.xml',
], ],
'assets': { 'assets': {
@@ -50,6 +53,8 @@
'sf_manufacturing/static/src/scss/kanban_change.scss', 'sf_manufacturing/static/src/scss/kanban_change.scss',
'sf_manufacturing/static/src/xml/button_show_on_tree.xml', '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/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/js/qr.js',
'sf_manufacturing/static/src/xml/qr.xml', 'sf_manufacturing/static/src/xml/qr.xml',
] ]

View File

@@ -27,7 +27,7 @@ class JikimoSaleRoutePicking(Sf_Bf_Connect):
bfm_process_order_list = json.loads(kw['bfm_process_order_list']) 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( 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'], 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 i = 1
# 给sale_order的default_code字段赋值 # 给sale_order的default_code字段赋值
# aa = request.env['sale.order'].sudo().search([('name', '=', order_id.name)]) # aa = request.env['sale.order'].sudo().search([('name', '=', order_id.name)])

View File

@@ -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"

View File

@@ -15,3 +15,4 @@ from . import sf_technology_design
from . import sf_production_common from . import sf_production_common
from . import sale_order from . import sale_order
from . import quick_easy_order from . import quick_easy_order
from . import purchase_order

View File

@@ -1,6 +1,7 @@
import logging import logging
import requests import requests
from datetime import timedelta
from odoo import models, fields, api, _ from odoo import models, fields, api, _
from odoo.exceptions import UserError from odoo.exceptions import UserError
@@ -210,9 +211,18 @@ class AgvScheduling(models.Model):
def button_cancel(self): def button_cancel(self):
# 弹出二次确认窗口后执行 # 弹出二次确认窗口后执行
for rec in self: for rec in self:
if rec.state != '待下发':
raise UserError('只有待下发状态的AGV调度任务才能取消')
rec.state = '已取消' 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): def finish_scheduling(self):
""" """
@@ -232,7 +242,7 @@ class AgvScheduling(models.Model):
agv_route sf.agv.task.route对象 agv_route sf.agv.task.route对象
""" """
for rec in self: for rec in self:
if rec.state != '待下发': if rec.state not in ['待下发', '配送中']:
return False return False
_logger.info('AGV任务调度下发调度任务路线为%s' % agv_task_route) _logger.info('AGV任务调度下发调度任务路线为%s' % agv_task_route)
rec.state = '配送中' rec.state = '配送中'
@@ -264,7 +274,45 @@ class AgvScheduling(models.Model):
'task_delivery_time': fields.Datetime.now() 'task_delivery_time': fields.Datetime.now()
}) })
return super().write(vals) 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): class ResMrpWorkOrder(models.Model):
_inherit = 'mrp.workorder' _inherit = 'mrp.workorder'

View File

@@ -75,7 +75,7 @@ class ManualProductModelTypeRoutingSort(models.Model):
_description = '成品工序排序(人工线下加工)' _description = '成品工序排序(人工线下加工)'
sequence = fields.Integer('Sequence') 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') is_repeat = fields.Boolean('重复', related='route_workcenter_id.is_repeat')
routing_type = fields.Selection(string="工序类型", related='route_workcenter_id.routing_type') 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') workcenter_ids = fields.Many2many('mrp.workcenter', required=False, related='route_workcenter_id.workcenter_ids')

View File

@@ -8,6 +8,7 @@ import re
import requests import requests
from itertools import groupby from itertools import groupby
from collections import defaultdict, namedtuple from collections import defaultdict, namedtuple
from odoo import api, fields, models, SUPERUSER_ID, _ from odoo import api, fields, models, SUPERUSER_ID, _
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError, ValidationError
from odoo.addons.sf_base.commons.common import Common from odoo.addons.sf_base.commons.common import Common
@@ -18,6 +19,7 @@ class MrpProduction(models.Model):
_inherit = 'mrp.production' _inherit = 'mrp.production'
_description = "制造订单" _description = "制造订单"
_order = 'create_date desc' _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') deadline_of_delivery = fields.Date('订单交期', tracking=True, compute='_compute_deadline_of_delivery')
# tray_ids = fields.One2many('sf.tray', 'production_id', string="托盘") # tray_ids = fields.One2many('sf.tray', 'production_id', string="托盘")
maintenance_count = fields.Integer(compute='_compute_maintenance_count', string="Number of maintenance requests") 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_remark = fields.Text(string='功能刀具状态备注(缺刀)', compute='_compute_tool_state_remark', store=True)
tool_state_remark2 = fields.Text(string='功能刀具状态备注(无效刀)', readonly=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') @api.depends('procurement_group_id.mrp_production_ids.move_dest_ids.group_id.sale_id')
def _compute_deadline_of_delivery(self): def _compute_deadline_of_delivery(self):
for production in self: for production in self:
@@ -338,24 +363,18 @@ class MrpProduction(models.Model):
# if production.state == 'pending_cam': # if production.state == 'pending_cam':
# if all(wo_state in 'done' for wo_state in production.workorder_ids.mapped('state')): # if all(wo_state in 'done' for wo_state in production.workorder_ids.mapped('state')):
# production.state = 'done' # production.state = 'done'
if any( 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 ['编程中', '已编程'])
wo.test_results == '返工' and wo.state == 'done' and production.programming_state in [ for wo in production.workorder_ids):
'已编程']) 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):
production.state = 'rework' production.state = 'rework'
if any(wo.test_results == '报废' and wo.state == 'done' for wo in production.workorder_ids): if any(wo.test_results == '报废' and wo.state == 'done' for wo in production.workorder_ids):
production.state = 'scrap' production.state = 'scrap'
if any(dr.test_results == '报废' and dr.handle_result == '已处理' for dr in if any(dr.test_results == '报废' and dr.handle_result == '已处理' for dr in
production.detection_result_ids): production.detection_result_ids):
production.state = 'cancel' production.state = 'cancel'
# 如果制造订单的功能刀具为【无效刀】则制造订单状态改为返工 if production.workorder_ids and all(wo_state in ('done', 'rework', 'cancel') for wo_state in production.workorder_ids.mapped('state')):
if production.tool_state == '2': if production.state not in ['scrap', 'rework', 'cancel']:
production.state = 'rework' production.state = 'done'
# 退回调整 # 退回调整
def technology_back_adjust(self): def technology_back_adjust(self):
@@ -378,22 +397,55 @@ class MrpProduction(models.Model):
if process_parameters: if process_parameters:
raise UserError(_("【工艺设计】-【参数】为%s的在【产品】中不存在,请先创建", ", ".join(process_parameters))) raise UserError(_("【工艺设计】-【参数】为%s的在【产品】中不存在,请先创建", ", ".join(process_parameters)))
if production_confirmed: if production_confirmed:
return { production_count = self.env['mrp.production'].search_count([
'name': _('退回调整'), ('origin', '=', self.origin),
'type': 'ir.actions.act_window', ('product_id', '=', self.product_id.id),
'view_mode': 'form', ('state', '=', 'confirmed')
'res_model': 'sf.production.technology.re_adjust.wizard', ])
'target': 'new', if production_count > 1:
'context': { return {
'default_production_id': self.id, 'name': _('退回调整'),
'default_origin': self.origin, '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): def technology_confirm(self):
process_parameters = [] process_parameters = []
account_moves = [] account_moves = []
parameters_not = [] 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( special_design = self.technology_design_ids.filtered(
lambda a: a.routing_tag == 'special' and a.is_auto is False) lambda a: a.routing_tag == 'special' and a.is_auto is False)
for special in special_design: for special in special_design:
@@ -405,11 +457,7 @@ class MrpProduction(models.Model):
if not product_production_process: if not product_production_process:
if special.process_parameters_id not in process_parameters: if special.process_parameters_id not in process_parameters:
process_parameters.append(special.process_parameters_id.display_name) 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: if account_moves:
raise UserError(_("请联系工厂生产经理对采购订单为%s生成的账单进行取消", ", ".join(account_moves))) raise UserError(_("请联系工厂生产经理对采购订单为%s生成的账单进行取消", ", ".join(account_moves)))
if parameters_not: if parameters_not:
@@ -441,16 +489,37 @@ class MrpProduction(models.Model):
error_panel.append(design.panel) error_panel.append(design.panel)
else: else:
if not error_panel and not process_parameters: if not error_panel and not process_parameters:
return { production_count = self.env['mrp.production'].search_count([
'name': _('工艺确认'), ('origin', '=', self.origin),
'type': 'ir.actions.act_window', ('product_id', '=', self.product_id.id),
'view_mode': 'form', ('state', '=', 'technology_to_confirmed')
'res_model': 'sf.production.technology.wizard', ])
'target': 'new', if production_count > 1:
'context': { return {
'default_production_id': self.id, 'name': _('工艺确认'),
'default_origin': self.origin, '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: if error_panel:
raise UserError(_("【加工面】为%s的标准工序顺序有误,请调整后重试", ", ".join(error_panel))) raise UserError(_("【加工面】为%s的标准工序顺序有误,请调整后重试", ", ".join(error_panel)))
return True return True
@@ -694,7 +763,8 @@ class MrpProduction(models.Model):
}] }]
if production.product_id.categ_id.type in ['成品', '坯料']: 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( workorder_has = self.env['mrp.workorder'].search(
[('technology_design_id', '=', route.id), ('production_id', '=', production.id)]) [('technology_design_id', '=', route.id), ('production_id', '=', production.id)])
if not workorder_has: if not workorder_has:
@@ -720,6 +790,7 @@ class MrpProduction(models.Model):
groupby(production_all, key=lambda x: x.product_id.id)} groupby(production_all, key=lambda x: x.product_id.id)}
for product_id, pd in grouped_product_ids.items(): for product_id, pd in grouped_product_ids.items():
product_id_to_production_names[product_id] = [p.name for p in pd] product_id_to_production_names[product_id] = [p.name for p in pd]
sorted_workorders = None
for production in production_all: for production in production_all:
proc_workorders = [] proc_workorders = []
process_parameter_workorder = self.env['mrp.workorder'].search( 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._get_surface_technics_purchase_ids().write({'state': 'cancel'})
workorder.move_subcontract_workorder_ids.write({'state': 'cancel'}) workorder.move_subcontract_workorder_ids.write({'state': 'cancel'})
workorder.move_subcontract_workorder_ids.picking_id.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) sorted_workorders = sorted(process_parameter_workorder, key=lambda w: w.sequence)
for i, workorder in enumerate(sorted_workorders): if not sorted_workorders:
# 检查当前工作订单和下一个工作订单是否连续,并且供应商相同 return
if i == 0: for workorders in reversed(sorted_workorders):
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):
self.env['stock.picking'].create_outcontract_picking(workorders, production, 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) self.env['purchase.order'].get_purchase_order(workorders, production, product_id_to_production_names)
# 工单排序 # 工单排序
def _reset_work_order_sequence1(self, k): def _reset_work_order_sequence1(self, k):
for rec in self: for rec in self:
@@ -887,10 +922,11 @@ class MrpProduction(models.Model):
work_ids = workorder_ids.filtered(lambda item: item.sequence == 0) work_ids = workorder_ids.filtered(lambda item: item.sequence == 0)
# 对工单进行逐个插入 # 对工单进行逐个插入
for work_id in work_ids: for work_id in work_ids:
for order_id in rec.workorder_ids.filtered(lambda item: item.sequence > 0): order_rework_ids = rec.workorder_ids.filtered(
if work_id.name == order_id.name: lambda item: (item.sequence > 0 and work_id.name == item.name
work_id.sequence = order_id.sequence + 1 and work_id.processing_panel == item.processing_panel))
break 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( work_order_ids = rec.workorder_ids.filtered(
lambda item: item.sequence >= work_id.sequence and item.id != work_id.id) 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 and item.process_parameters_id == work.surface_technics_parameters_id) or
(item.route_id.name == work.name and item.panel (item.route_id.name == work.name and item.panel
and item.panel == work.processing_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: if td_ids:
work.sequence = td_ids[0].sequence work.sequence = td_ids[0].sequence
cancel_work_ids = workorder_ids.filtered(lambda item: item.state in ('已取消', 'cancel')) 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 lambda wk: (wk.name == result_id.routing_type and wk.processing_panel == result_id.processing_panel
and wk.state == 'done')).id and wk.state == 'done')).id
for result_id in result_ids] 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 { return {
'name': _('返工'), 'name': _('返工'),
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
@@ -1179,7 +1219,7 @@ class MrpProduction(models.Model):
'target': 'new', 'target': 'new',
'context': { 'context': {
'default_production_id': self.id, '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_hidden_workorder_ids': ','.join(map(str, work_id_list)) if work_id_list != [] else '',
'default_reprogramming_num': cloud_programming['reprogramming_num'], 'default_reprogramming_num': cloud_programming['reprogramming_num'],
'default_programming_state': cloud_programming['programming_state'], 'default_programming_state': cloud_programming['programming_state'],
@@ -1435,6 +1475,26 @@ class MrpProduction(models.Model):
for production in self: for production in self:
production.production_type = '自动化产线加工' if not production.product_id.is_manual_processing else '人工线下加工' 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 @api.model_create_multi
def create(self, vals_list): def create(self, vals_list):
""" """
@@ -1495,7 +1555,9 @@ class MrpProduction(models.Model):
def action_view_purchase_orders(self): def action_view_purchase_orders(self):
self.ensure_one() 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( production = self.env['mrp.production'].search(
[('origin', '=', self.origin), ('product_id', '=', self.product_id.id)], limit=1, order='id asc') [('origin', '=', self.origin), ('product_id', '=', self.product_id.id)], limit=1, order='id asc')
else: else:
@@ -1519,6 +1581,26 @@ class MrpProduction(models.Model):
}) })
return action 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): class sf_detection_result(models.Model):
_name = 'sf.detection.result' _name = 'sf.detection.result'

View File

@@ -94,4 +94,10 @@ class ResMrpRoutingWorkcenter(models.Model):
route_ids.append(t.route_id.surface_technics_id.id) route_ids.append(t.route_id.surface_technics_id.id)
domain = [('id', 'not in', route_ids), ('routing_tag', '=', 'special')] domain = [('id', 'not in', route_ids), ('routing_tag', '=', 'special')]
return self._search(domain, limit=limit, access_rights_uid=name_get_uid) 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) return super()._name_search(name, args, operator, limit, name_get_uid)

View File

@@ -17,6 +17,7 @@ from odoo.exceptions import UserError, ValidationError
from odoo.addons.sf_mrs_connect.models.ftp_operate import FtpController from odoo.addons.sf_mrs_connect.models.ftp_operate import FtpController
class ResMrpWorkOrder(models.Model): class ResMrpWorkOrder(models.Model):
_inherit = 'mrp.workorder' _inherit = 'mrp.workorder'
_order = 'sequence asc' _order = 'sequence asc'
@@ -36,7 +37,7 @@ class ResMrpWorkOrder(models.Model):
store=True, check_company=True, string="材料") store=True, check_company=True, string="材料")
product_tmpl_id_materials_type_id = fields.Many2one(related='production_id.product_tmpl_id.materials_type_id', 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="型号") 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") users_ids = fields.Many2many("res.users", 'users_workorder', related="workcenter_id.users_ids")
processing_panel = fields.Char('加工面') processing_panel = fields.Char('加工面')
sequence = fields.Integer(string='工序') sequence = fields.Integer(string='工序')
@@ -129,7 +130,7 @@ class ResMrpWorkOrder(models.Model):
Y10_axis = fields.Float(default=0) Y10_axis = fields.Float(default=0)
Z10_axis = fields.Float(default=0) Z10_axis = fields.Float(default=0)
X_deviation_angle = fields.Integer(string="X轴偏差度", default=0) X_deviation_angle = fields.Integer(string="X轴偏差度", default=0)
test_results = fields.Selection([("合格", "合格")], default='合格', test_results = fields.Selection([("合格", "合格"), ("返工", "返工")], default='合格',
string="检测结果", tracking=True) string="检测结果", tracking=True)
cnc_ids = fields.One2many("sf.cnc.processing", 'workorder_id', string="CNC加工程序") cnc_ids = fields.One2many("sf.cnc.processing", 'workorder_id', string="CNC加工程序")
cmm_ids = fields.One2many("sf.cmm.program", 'workorder_id', string="CMM程序") cmm_ids = fields.One2many("sf.cmm.program", 'workorder_id', string="CMM程序")
@@ -138,7 +139,8 @@ class ResMrpWorkOrder(models.Model):
is_subcontract = fields.Boolean(string='是否外协') is_subcontract = fields.Boolean(string='是否外协')
surface_technics_parameters_id = fields.Many2one('sf.production.process.parameter', 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='外协采购单') purchase_id = fields.Many2many('purchase.order', string='外协采购单')
surface_technics_picking_count = fields.Integer("外协出入库", compute='_compute_surface_technics_picking_ids') surface_technics_picking_count = fields.Integer("外协出入库", compute='_compute_surface_technics_picking_ids')
@@ -327,9 +329,9 @@ class ResMrpWorkOrder(models.Model):
'view_mode': 'form', 'view_mode': 'form',
} }
return result return result
def _get_surface_technics_purchase_ids(self): 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 = self.env['purchase.order'].search(domain)
purchase_orders_id = self.env['purchase.order'] purchase_orders_id = self.env['purchase.order']
for po in purchase_orders: for po in purchase_orders:
@@ -1034,49 +1036,6 @@ class ResMrpWorkOrder(models.Model):
'production_id.tool_state', 'production_id.schedule_state', 'sequence', 'production_id.tool_state', 'production_id.schedule_state', 'sequence',
'production_id.programming_state') 'production_id.programming_state')
def _compute_state(self): 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: for workorder in self:
# 如果工单的工序没有进行排序则跳出循环 # 如果工单的工序没有进行排序则跳出循环
if workorder.production_id.workorder_ids.filtered(lambda wk: wk.sequence == 0): if workorder.production_id.workorder_ids.filtered(lambda wk: wk.sequence == 0):
@@ -1110,13 +1069,11 @@ class ResMrpWorkOrder(models.Model):
continue continue
else: else:
workorder.state = 'ready' workorder.state = 'ready'
continue continue
# ================= 如果制造订单刀具状态为[无效刀、缺刀] 或者 制造订单状态为[返工]========================== # ================= 如果制造订单刀具状态为[无效刀、缺刀] 或者 制造订单状态为[返工]==========================
if (workorder.production_id.tool_state in ['1', '2'] or workorder.production_id.state == 'rework' 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.schedule_state != '已排'
or workorder.production_id.reservation_state not in ['assigned'] 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 ['报废', '返工']):
if workorder.state != 'waiting': if workorder.state != 'waiting':
workorder.state = 'waiting' workorder.state = 'waiting'
continue continue
@@ -1135,86 +1092,12 @@ class ResMrpWorkOrder(models.Model):
if workorder.is_subcontract is False: if workorder.is_subcontract is False:
workorder.state = 'ready' workorder.state = 'ready'
else: 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() purchase_orders_id = self._get_surface_technics_purchase_ids()
if purchase_orders_id: if purchase_orders_id:
workorder.state = 'ready' if purchase_orders_id.state == 'purchase' else 'waiting' workorder.state = 'ready' if purchase_orders_id.state == 'purchase' else 'waiting'
else: else:
workorder.state = 'waiting' 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): def button_start(self):
# 判断工单状态是否为等待组件 # 判断工单状态是否为等待组件
@@ -1425,13 +1308,14 @@ class ResMrpWorkOrder(models.Model):
len(done_workorder) == len(record.production_id.workorder_ids)): len(done_workorder) == len(record.production_id.workorder_ids)):
is_production_id = True is_production_id = True
if record.routing_type in ['解除装夹'] or ( if record.routing_type in ['解除装夹'] or (
record.is_rework is True and record.routing_type in ['装夹预调']) or ( record.is_rework is True and record.routing_type in ['装夹预调']):
record.test_results in ['返工', '报废'] and record.routing_type in ['CNC加工']):
for workorder in record.production_id.workorder_ids: for workorder in record.production_id.workorder_ids:
if workorder.processing_panel == record.processing_panel: if workorder.processing_panel == record.processing_panel:
rfid_code = workorder.rfid_code rfid_code = workorder.rfid_code
workorder.write({'rfid_code_old': rfid_code, workorder.write({'rfid_code_old': rfid_code,
'rfid_code': False}) 'rfid_code': False})
self.env['stock.lot'].sudo().search([('rfid', '=', rfid_code)]).write(
{'tool_material_status': '可用'})
if workorder.rfid_code: if workorder.rfid_code:
raise ValidationError(f'{workorder.name}】工单解绑失败,请重新点击完成按钮!!!') raise ValidationError(f'{workorder.name}】工单解绑失败,请重新点击完成按钮!!!')
# workorder.rfid_code_old = rfid_code # workorder.rfid_code_old = rfid_code
@@ -1456,6 +1340,9 @@ class ResMrpWorkOrder(models.Model):
# 解绑托盘 # 解绑托盘
def unbind_tray(self): 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({ self.production_id.workorder_ids.write({
'rfid_code': False, 'rfid_code': False,
'tray_serial_number': False, 'tray_serial_number': False,
@@ -1570,7 +1457,7 @@ class ResMrpWorkOrder(models.Model):
'default_confirm_button': '确认解除', 'default_confirm_button': '确认解除',
# 'default_feeder_station_start_id': feeder_station_start_id, # 'default_feeder_station_start_id': feeder_station_start_id,
}} }}
move_subcontract_workorder_ids = fields.One2many('stock.move', 'subcontract_workorder_id', string='组件') move_subcontract_workorder_ids = fields.One2many('stock.move', 'subcontract_workorder_id', string='组件')
@@ -1781,6 +1668,7 @@ class SfWorkOrderBarcodes(models.Model):
if workorder_rfid: if workorder_rfid:
for item in workorder_rfid: for item in workorder_rfid:
item.write({'rfid_code': barcode}) item.write({'rfid_code': barcode})
lot.sudo().write({'tool_material_status': '在用'})
logging.info("Rfid[%s]绑定成功!!!" % barcode) logging.info("Rfid[%s]绑定成功!!!" % barcode)
else: else:
raise UserError('该Rfid【%s】绑定的是【%s】, 不是托盘!!!' % (barcode, lot.product_id.name)) raise UserError('该Rfid【%s】绑定的是【%s】, 不是托盘!!!' % (barcode, lot.product_id.name))

View File

@@ -774,11 +774,10 @@ class ResProductMo(models.Model):
# bfm下单 # bfm下单
manual_quotation = fields.Boolean('人工编程', default=False, readonly=True) manual_quotation = fields.Boolean('人工编程', default=False, readonly=True)
part_number = fields.Char(string='零件图号', readonly=True)
machining_drawings = fields.Binary('2D加工图纸', readonly=True) machining_drawings = fields.Binary('2D加工图纸', readonly=True)
quality_standard = fields.Binary('质检标准', readonly=True) quality_standard = fields.Binary('质检标准', readonly=True)
part_name = fields.Char(string='零件名称', readonly=True) part_name = fields.Char(string='零件名称', readonly=True)
part_number = fields.Char(string='零件图号', readonly=True)
@api.constrains('tool_length') @api.constrains('tool_length')
def _check_tool_length_size(self): def _check_tool_length_size(self):
if self.tool_length > 1000000: if self.tool_length > 1000000:
@@ -892,7 +891,7 @@ class ResProductMo(models.Model):
'machining_drawings': '' if not item['machining_drawings'] else base64.b64decode( 'machining_drawings': '' if not item['machining_drawings'] else base64.b64decode(
item['machining_drawings']), item['machining_drawings']),
'quality_standard': '' if not item['quality_standard'] else base64.b64decode(item['quality_standard']), '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( tax_id = self.env['account.tax'].sudo().search(
[('type_tax_use', '=', 'sale'), ('amount', '=', item.get('tax')), ('price_include', '=', 'True')]) [('type_tax_use', '=', 'sale'), ('amount', '=', item.get('tax')), ('price_include', '=', 'True')])

View File

@@ -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

View File

@@ -13,6 +13,9 @@ class SaleOrder(models.Model):
('sent', "报价已发送"), ('sent', "报价已发送"),
('supply method', "供货方式待确认"), ('supply method', "供货方式待确认"),
('sale', "销售订单"), ('sale', "销售订单"),
('processing', "加工中"),
('physical_distribution', "物流中"),
('delivered', "已交付"),
('done', "已锁定"), ('done', "已锁定"),
('cancel', "已取消"), ('cancel', "已取消"),
]) ])
@@ -149,7 +152,7 @@ class SaleOrder(models.Model):
class SaleOrderLine(models.Model): class SaleOrderLine(models.Model):
_inherit = 'sale.order.line' _inherit = 'sale.order.line'
part_number = fields.Char('零件图号', related='product_id.part_number', readonly=True)
# 供货方式 # 供货方式
supply_method = fields.Selection([ supply_method = fields.Selection([
('automation', "自动化产线加工"), ('automation', "自动化产线加工"),

View File

@@ -1,4 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from collections import Counter
from odoo import fields, models, api, _ from odoo import fields, models, api, _
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
@@ -6,7 +8,7 @@ from odoo.exceptions import ValidationError
class sf_technology_design(models.Model): class sf_technology_design(models.Model):
_name = 'sf.technology.design' _name = 'sf.technology.design'
_description = "工艺设计" _description = "工艺设计"
group_uniq_id = fields.Integer('同一制造订单唯一id',default=0)
sequence = fields.Integer('序号') sequence = fields.Integer('序号')
route_id = fields.Many2one('mrp.routing.workcenter', '工序') route_id = fields.Many2one('mrp.routing.workcenter', '工序')
process_parameters_id = fields.Many2one('sf.production.process.parameter', string='表面工艺参数') 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) is_auto = fields.Boolean('是否自动生成', default=False)
active = fields.Boolean('有效', default=True) 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): def json_technology_design_str(self, k, route, i, process_parameter):
workorders_values_str = [0, '', { workorders_values_str = [0, '', {
'route_id': route.id if route.routing_type in ['表面工艺'] else route.route_workcenter_id.id, '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}] 'is_auto': True}]
return workorders_values_str return workorders_values_str
def write(self, vals):
return super(sf_technology_design, self).write(vals)
def unlink_technology_design(self): def unlink_technology_design(self):
self.active = False self.active = False
@@ -37,4 +46,64 @@ class sf_technology_design(models.Model):
for vals in vals_list: for vals in vals_list:
if not vals.get('route_id'): if not vals.get('route_id'):
raise ValidationError(_("工序不能为空")) 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)

View File

@@ -588,40 +588,34 @@ class StockPicking(models.Model):
address_of_delivery = fields.Char('联系地址', compute='_compute_move_ids', store=True) address_of_delivery = fields.Char('联系地址', compute='_compute_move_ids', store=True)
retrospect_ref = 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') picking_type_sequence_code = fields.Char(related='picking_type_id.sequence_code')
@api.depends('move_ids', 'move_ids.product_id') @api.depends('move_ids', 'move_ids.product_id')
def _compute_move_ids(self): def _compute_move_ids(self):
for item in self: for item in self:
if item.move_ids: if item.move_ids:
if item.picking_type_id.sequence_code == 'DL': product_id = item.move_ids[0].product_id
sale_name = item.move_ids[0].product_id.name.split('-')[1] if product_id:
if 'S' in sale_name: sale_info = None
sale_id = self.env['sale.order'].sudo().search([('name', '=', sale_name)]) if product_id.categ_id.type == '坯料' and product_id.name.startswith('R-S'):
item.person_of_delivery = sale_id.person_of_delivery parts = product_id.name.split('-')
item.telephone_of_delivery = sale_id.telephone_of_delivery if len(parts) >= 3:
item.address_of_delivery = sale_id.address_of_delivery sale_name = parts[1]
sale_info = self.env['sale.order'].sudo().search(
[('name', '=', sale_name)])
else: else:
raise ValidationError('坯料名称格式错误,正确格式为[R-S???-?]') sale_order_line = self.env['sale.order.line'].sudo().search(
move_ids = [] [('product_id', '=', product_id.id)])
for move_id in item.move_ids: if sale_order_line:
move_ids.append(move_id.product_id.id) sale_info = sale_order_line[0].order_id
boms = self.env['mrp.bom'].sudo().search([('bom_line_ids.product_id', 'in', move_ids)]) if sale_info:
if boms: item.sale_order_id = sale_info.id
codes_list = [] item.retrospect_ref = sale_info.order_code
for bom in boms: if item.picking_type_id.sequence_code == 'DL':
if bom.product_tmpl_id.default_code: item.person_of_delivery = sale_info.person_of_delivery
code_list = bom.product_tmpl_id.default_code.split('-') item.telephone_of_delivery = sale_info.telephone_of_delivery
if len(code_list) >= 4: item.address_of_delivery = sale_info.address_of_delivery
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
# 设置外协出入单的名称 # 设置外协出入单的名称
def _get_name_Res(self, rescode): def _get_name_Res(self, rescode):
@@ -644,58 +638,80 @@ class StockPicking(models.Model):
workorder = move_in.subcontract_workorder_id workorder = move_in.subcontract_workorder_id
workorders = workorder.production_id.workorder_ids workorders = workorder.production_id.workorder_ids
subcontract_workorders = workorders.filtered(lambda wo: wo.is_subcontract == True).sorted('sequence') 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( 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 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 return res
# 创建 外协出库入单 # 创建 外协出库入单
def create_outcontract_picking(self, workorders, item, sorted_workorders): def create_outcontract_picking(self, workorders, item, sorted_workorders):
for workorder in workorders: for workorder in workorders:
if workorder.move_subcontract_workorder_ids: if workorder.move_subcontract_workorder_ids:
workorder.move_subcontract_workorder_ids.write({'state': 'draft'}) workorder.move_subcontract_workorder_ids.write({'state': 'cancel'})
workorder.move_subcontract_workorder_ids.picking_id.write({'state': 'draft'}) 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: else:
# 创建一个新的补货组 # 从sorted_workorders中找到上一工单的move
procurement_group_id = self.env['procurement.group'].create({ if len(sorted_workorders) > 1:
'name': workorder.name, move_dest_id = \
'partner_id': self.partner_id.id, sorted_workorders[sorted_workorders.index(workorder) + 1].move_subcontract_workorder_ids[1].id
}) new_picking = True
move_dest_id = False outcontract_picking_type_in = self.env.ref(
# 如果当前工单是是制造订单的最后一个工单 'sf_manufacturing.outcontract_picking_in').id,
if workorder == item.workorder_ids[-1]: outcontract_picking_type_out = self.env.ref(
move_dest_id = item.move_raw_ids[0].id 'sf_manufacturing.outcontract_picking_out').id,
else: context = dict(self.env.context)
# 从sorted_workorders中找到上一工单的move context.update({
if sorted_workorders.index(workorder) > 0: 'default_production_id': item.id, # 添加额外信息到 context 中
move_dest_id = sorted_workorders[sorted_workorders.index(workorder) - 1].move_subcontract_workorder_ids[1].id })
new_picking = True moves_in = self.env['stock.move'].sudo().with_context(context).create(
outcontract_picking_type_in = self.env.ref( self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_in,
'sf_manufacturing.outcontract_picking_in').id, procurement_group_id.id, move_dest_id))
outcontract_picking_type_out = self.env.ref( picking_in = self.create(
'sf_manufacturing.outcontract_picking_out').id, moves_in._get_new_picking_values_Res(item, workorder, 'WH/OCIN/'))
moves_in = self.env['stock.move'].sudo().create( # pick_ids.append(picking_in.id)
self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_in, procurement_group_id.id, move_dest_id)) moves_in.write(
picking_in = self.create( {'picking_id': picking_in.id, 'state': 'waiting'})
moves_in._get_new_picking_values_Res(item, workorder, 'WH/OCIN/')) moves_in._assign_picking_post_process(new=new_picking)
# pick_ids.append(picking_in.id) # self.env.context.get('default_production_id')
moves_in.write( moves_out = self.env['stock.move'].sudo().with_context(context).create(
{'picking_id': picking_in.id, 'state': 'waiting'}) self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_out,
moves_in._assign_picking_post_process(new=new_picking) procurement_group_id.id, moves_in.id))
moves_out = self.env['stock.move'].sudo().create( workorder.write({'move_subcontract_workorder_ids': [(6, 0, [moves_in.id, moves_out.id])]})
self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_out, procurement_group_id.id, moves_in.id)) picking_out = self.create(
workorder.write({'move_subcontract_workorder_ids': [(6, 0, [moves_in.id, moves_out.id])]}) moves_out._get_new_picking_values_Res(item, workorder, 'WH/OCOUT/'))
picking_out = self.create( # pick_ids.append(picking_out.id)
moves_out._get_new_picking_values_Res(item, workorder, 'WH/OCOUT/')) moves_out.write(
# pick_ids.append(picking_out.id) {'picking_id': picking_out.id, 'state': 'waiting'})
moves_out.write( moves_out._assign_picking_post_process(new=new_picking)
{'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') @api.depends('move_type', 'immediate_transfer', 'move_ids.state', 'move_ids.picking_id')
def _compute_state(self): def _compute_state(self):
super(StockPicking, self)._compute_state() super(StockPicking, self)._compute_state()
@@ -706,7 +722,9 @@ class StockPicking(models.Model):
if picking.move_ids: if picking.move_ids:
workorder = picking.move_ids[0].subcontract_workorder_id workorder = picking.move_ids[0].subcontract_workorder_id
if picking.state == 'assigned': 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' picking.state = 'waiting'
@@ -716,10 +734,39 @@ class ReStockMove(models.Model):
materiel_length = fields.Float(string='物料长度', digits=(16, 4)) materiel_length = fields.Float(string='物料长度', digits=(16, 4))
materiel_width = fields.Float(string='物料宽度', digits=(16, 4)) materiel_width = fields.Float(string='物料宽度', digits=(16, 4))
materiel_height = 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): 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 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 = { move_values = {
'name': '', 'name': '',
'company_id': item.company_id.id, 'company_id': item.company_id.id,
@@ -731,6 +778,7 @@ class ReStockMove(models.Model):
'origin': item.name, 'origin': item.name,
'group_id': group_id, 'group_id': group_id,
'move_dest_ids': [(6, 0, [move_dest_ids])] if move_dest_ids else False, '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)], # 'route_ids': False if not route else [(4, route.id)],
'date_deadline': datetime.now(), 'date_deadline': datetime.now(),
'picking_type_id': picking_type_id, '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') production = self.env['mrp.production'].search([('name', '=', self[0].origin)], limit=1, order='id asc')
productions = self.env['mrp.production'].search( productions = self.env['mrp.production'].search(
[('origin', '=', production.origin), ('product_id', '=', production.product_id.id)]) [('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 res['retrospect_ref'] = production.product_id.name
return res 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): class ReStockQuant(models.Model):

View File

@@ -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_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_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_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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
182
183
184
185
186
187
188
189

View File

@@ -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: $('<div>').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;
});

View File

@@ -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: $('<div>').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;
});

View File

@@ -22,8 +22,7 @@ odoo.define('sf_manufacturing.action_dispatch_confirm', function (require) {
rpc.query({ rpc.query({
model: 'sf.workpiece.delivery.wizard', model: 'sf.workpiece.delivery.wizard',
method: 'confirm', method: 'confirm',
args: [params.active_id] args: [params.active_id],
,
kwargs: { kwargs: {
context: params.context, context: params.context,
} }

View File

@@ -33,6 +33,20 @@
class="btn-danger" class="btn-danger"
confirm="你确定要取消这条记录吗?" confirm="你确定要取消这条记录吗?"
/> />
<button
name="button_cancel_confirm"
string="取消" type="object"
attrs="{'invisible': [('state', '!=', '配送中')]}"
icon="fa-times"
class="btn-danger"
/>
<button
name="button_resend_confirm"
string="重新下发" type="object"
attrs="{'invisible': [('state', '!=', '配送中')]}"
icon="fa-circle-o-notch"
class="btn-success "
/>
</tree> </tree>
</field> </field>
</record> </record>

View File

@@ -3,7 +3,7 @@
<data> <data>
#------------------模型类型------------------ #------------------模型类型------------------
<record model="ir.ui.view" id="search_sf_model_type_view"> <record model="ir.ui.view" id="search_sf_model_name_view">
<field name="name">search.sf.model.type</field> <field name="name">search.sf.model.type</field>
<field name="model">sf.model.type</field> <field name="model">sf.model.type</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
@@ -14,7 +14,7 @@
</field> </field>
</record> </record>
<record model="ir.ui.view" id="tree_sf_model_type_view"> <record model="ir.ui.view" id="tree_sf_model_name_view">
<field name="name">tree.sf.model.type</field> <field name="name">tree.sf.model.type</field>
<field name="model">sf.model.type</field> <field name="model">sf.model.type</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
@@ -24,7 +24,7 @@
</field> </field>
</record> </record>
<record model="ir.ui.view" id="form_sf_model_type"> <record model="ir.ui.view" id="form_sf_model_name">
<field name="name">form.sf.model.type</field> <field name="name">form.sf.model.type</field>
<field name="model">sf.model.type</field> <field name="model">sf.model.type</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
@@ -37,7 +37,7 @@
<field name='product_routing_tmpl_ids'> <field name='product_routing_tmpl_ids'>
<tree editable='bottom'> <tree editable='bottom'>
<field name="sequence" widget="handle" string="序号"/> <field name="sequence" widget="handle" string="序号"/>
<field name="route_workcenter_id" string="工序" options="{'no_create': True}"/> <field name="route_workcenter_id" string="工序" options="{'no_create': True}" context="{'is_duplicate': True, 'model_name': 'sf.product.model.type.routing.sort'}"/>
<field name="routing_type" string="类型"/> <field name="routing_type" string="类型"/>
<field name="is_repeat" string="重复"/> <field name="is_repeat" string="重复"/>
<field name="workcenter_ids" string="工作中心" widget="many2many_tags"/> <field name="workcenter_ids" string="工作中心" widget="many2many_tags"/>
@@ -48,7 +48,7 @@
<field name='manual_product_routing_tmpl_ids'> <field name='manual_product_routing_tmpl_ids'>
<tree editable='bottom'> <tree editable='bottom'>
<field name="sequence" widget="handle" string="序号"/> <field name="sequence" widget="handle" string="序号"/>
<field name="route_workcenter_id" string="工序" options="{'no_create': True}"/> <field name="route_workcenter_id" string="工序" options="{'no_create': True}" context="{'is_duplicate': True, 'model_name': 'sf.manual.product.model.type.routing.sort'}"/>
<field name="routing_type" string="类型"/> <field name="routing_type" string="类型"/>
<field name="is_repeat" string="重复"/> <field name="is_repeat" string="重复"/>
<field name="workcenter_ids" string="工作中心" widget="many2many_tags"/> <field name="workcenter_ids" string="工作中心" widget="many2many_tags"/>
@@ -59,7 +59,7 @@
<field name='embryo_routing_tmpl_ids'> <field name='embryo_routing_tmpl_ids'>
<tree editable='bottom'> <tree editable='bottom'>
<field name="sequence" widget="handle" string="序号"/> <field name="sequence" widget="handle" string="序号"/>
<field name="route_workcenter_id" string="工序" options="{'no_create': True}"/> <field name="route_workcenter_id" string="工序" options="{'no_create': True}" context="{'is_duplicate': True, 'model_name': 'sf.embryo.model.type.routing.sort'}"/>
<field name="routing_type" string="类型"/> <field name="routing_type" string="类型"/>
<field name="is_repeat" string="重复"/> <field name="is_repeat" string="重复"/>
<field name="workcenter_ids" string="工作中心" widget="many2many_tags"/> <field name="workcenter_ids" string="工作中心" widget="many2many_tags"/>
@@ -70,7 +70,7 @@
<field name='surface_technics_routing_tmpl_ids' style="white-space: pre-wrap;"> <field name='surface_technics_routing_tmpl_ids' style="white-space: pre-wrap;">
<tree editable='bottom'> <tree editable='bottom'>
<field name="sequence" widget="handle" string="序号"/> <field name="sequence" widget="handle" string="序号"/>
<field name="route_workcenter_id" string="工序" options="{'no_create': True}"/> <field name="route_workcenter_id" string="工序" options="{'no_create': True}" context="{'is_duplicate': True, 'model_name': 'sf.surface_technics.model.type.routing.sort'}"/>
<field name="routing_type" string="类型"/> <field name="routing_type" string="类型"/>
<field name="is_repeat" string="重复"/> <field name="is_repeat" string="重复"/>
<field name="workcenter_ids" string="工作中心" widget="many2many_tags"/> <field name="workcenter_ids" string="工作中心" widget="many2many_tags"/>
@@ -81,7 +81,7 @@
</field> </field>
</record> </record>
<record id="action_sf_model_type" model="ir.actions.act_window"> <record id="action_sf_model_name" model="ir.actions.act_window">
<field name="name">模型类型</field> <field name="name">模型类型</field>
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>
<field name="res_model">sf.model.type</field> <field name="res_model">sf.model.type</field>
@@ -96,11 +96,11 @@
</record> </record>
<menuitem <menuitem
id="menu_sf_model_type" id="menu_sf_model_name"
name="模型类型" name="模型类型"
parent="mrp.menu_mrp_configuration" parent="mrp.menu_mrp_configuration"
sequence="9" sequence="9"
action="action_sf_model_type" action="action_sf_model_name"
/> />
</data> </data>
</odoo> </odoo>

View File

@@ -18,9 +18,12 @@
<xpath expr="//field[@name='date_deadline']" position="replace"/> <xpath expr="//field[@name='date_deadline']" position="replace"/>
<xpath expr="//field[@name='name']" position="after"> <xpath expr="//field[@name='name']" position="after">
<field name="product_id" readonly="1" optional="show"/> <field name="product_id" readonly="1" optional="show"/>
<field name="part_name" readonly="1" optional="hide"/>
<field name="part_number" readonly="1" optional="show"/>
</xpath> </xpath>
<xpath expr="//field[@name='product_id']" position="after"> <xpath expr="//field[@name='product_id']" position="after">
<field name="product_qty" sum="Total Qty" string="数量" readonly="1" optional="show"/> <field name="product_qty" sum="Total Qty" string="数量" readonly="1" optional="show"/>
<field name="deadline_of_delivery" optional="show"/>
</xpath> </xpath>
<xpath expr="//field[@name='product_qty']" position="after"> <xpath expr="//field[@name='product_qty']" position="after">
<field name="product_uom_id" string="计量单位" options="{'no_open':True,'no_create':True}" <field name="product_uom_id" string="计量单位" options="{'no_open':True,'no_create':True}"
@@ -43,6 +46,7 @@
<field name="activity_ids" string="下一个活动" widget="list_activity" optional="hide"/> <field name="activity_ids" string="下一个活动" widget="list_activity" optional="hide"/>
</xpath> </xpath>
<xpath expr="//field[@name='origin']" position="replace"> <xpath expr="//field[@name='origin']" position="replace">
<field name="sale_order_id" optional="show"/>
<field name="origin" optional="hide"/> <field name="origin" optional="hide"/>
</xpath> </xpath>
<xpath expr="//field[@name='components_availability']" position="replace"> <xpath expr="//field[@name='components_availability']" position="replace">
@@ -123,14 +127,10 @@
<field name="tool_state" <field name="tool_state"
attrs="{'invisible': [('production_type', 'not in', ['自动化产线加工'])]}"/> attrs="{'invisible': [('production_type', 'not in', ['自动化产线加工'])]}"/>
<field name="tool_state_remark" string="备注" attrs="{'invisible': [('tool_state', '!=', '1')]}"/> <field name="tool_state_remark" string="备注" attrs="{'invisible': [('tool_state', '!=', '1')]}"/>
<field name="sale_order_id" readonly="1"/>
<field name="deadline_of_delivery" readonly="1"/> <field name="deadline_of_delivery" readonly="1"/>
<field name="tool_state_remark2" invisible="1"/> <field name="tool_state_remark2" invisible="1"/>
</xpath> </xpath>
<xpath expr="//header//button[@name='action_cancel']" position="replace">
<button name="action_cancel" type="object" string="取消" data-hotkey="z"
attrs="{'invisible': ['|', '|', ('id', '=', False), ('state', 'in', ('done', 'cancel')), ('confirm_cancel', '=', True)]}"
groups="sf_base.group_sf_mrp_user"/>
</xpath>
<xpath expr="(//header//button[@name='button_mark_done'])[1]" position="replace"> <xpath expr="(//header//button[@name='button_mark_done'])[1]" position="replace">
<button name="button_mark_done" <button name="button_mark_done"
attrs="{'invisible': ['|', '|', ('state', 'in', ('draft', 'cancel', 'done', 'to_close')), ('qty_producing', '=', 0), ('move_raw_ids', '!=', [])]}" attrs="{'invisible': ['|', '|', ('state', 'in', ('draft', 'cancel', 'done', 'to_close')), ('qty_producing', '=', 0), ('move_raw_ids', '!=', [])]}"
@@ -255,13 +255,6 @@
type="object" groups="sf_base.group_sf_mrp_user"/> type="object" groups="sf_base.group_sf_mrp_user"/>
</xpath> </xpath>
<xpath expr="//header//button[@name='action_cancel']" position="replace">
<button name="action_cancel" type="object" string="Cancel" data-hotkey="z"
attrs="{'invisible': ['|', '|', ('id', '=', False), ('state', 'in', ('done', 'cancel')), ('confirm_cancel', '=', False)]}"
confirm="Some product moves have already been confirmed, this manufacturing order can't be completely cancelled. Are you still sure you want to process ?"
groups="sf_base.group_sf_mrp_user"/>
</xpath>
<xpath expr="//header//button[@name='button_unbuild']" position="replace"> <xpath expr="//header//button[@name='button_unbuild']" position="replace">
<button name="button_unbuild" type="object" string="拆单" <button name="button_unbuild" type="object" string="拆单"
attrs="{'invisible': [('state', '!=', 'done')]}" data-hotkey="shift+v" attrs="{'invisible': [('state', '!=', 'done')]}" data-hotkey="shift+v"
@@ -305,12 +298,14 @@
type="object" groups="sf_base.group_sf_mrp_user"/> type="object" groups="sf_base.group_sf_mrp_user"/>
</xpath> </xpath>
<xpath expr="//header//button[@name='action_cancel']" position="replace"> <xpath expr="//header//button[@name='action_cancel'][2]" position="replace">
<button name="action_cancel" type="object" string="取消" data-hotkey="z" <button name="action_cancel" type="object" string="取消" data-hotkey="z"
attrs="{'invisible': ['|', '|', ('id', '=', False), ('state', 'in', ('done', 'cancel')), ('confirm_cancel', '=', False)]}" attrs="{'invisible': ['|', '|', ('id', '=', False), ('state', 'in', ('done', 'rework', 'cancel')), ('confirm_cancel', '=', False)]}"
confirm="Some product moves have already been confirmed, this manufacturing order can't be completely cancelled. Are you still sure you want to process ?" confirm="Some product moves have already been confirmed, this manufacturing order can't be completely cancelled. Are you still sure you want to process ?"
groups="sf_base.group_sf_mrp_user"/> groups="sf_base.group_sf_mrp_user"/>
</xpath> </xpath>
<xpath expr="//header//button[@name='action_cancel'][1]" position="replace"></xpath>
<xpath expr="//header//button[@name='button_unbuild']" position="replace"> <xpath expr="//header//button[@name='button_unbuild']" position="replace">
<button name="button_unbuild" type="object" string="拆单" <button name="button_unbuild" type="object" string="拆单"
attrs="{'invisible': [('state', '!=', 'done')]}" data-hotkey="shift+v" attrs="{'invisible': [('state', '!=', 'done')]}" data-hotkey="shift+v"
@@ -439,9 +434,7 @@
<xpath expr="//tree//button[@name='button_start']" position="replace"> <xpath expr="//tree//button[@name='button_start']" position="replace">
<field name="routing_type" invisible="True"/> <field name="routing_type" invisible="True"/>
<button name="button_start" type="object" string="开始" class="btn-success" confirm="是否确认开始?" <button name="button_start" type="object" string="开始" class="btn-success" confirm="是否确认开始?"
attrs="{'invisible': ['|', '|', '|','|', ('production_state','in', ('draft', 'done', 'cancel')), attrs="{'invisible': [('state', '!=', 'ready')]}"
('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel')), ('is_user_working', '!=',
False), ('routing_type', '=', 'CNC加工')]}"
groups="sf_base.group_sf_mrp_user"/> groups="sf_base.group_sf_mrp_user"/>
</xpath> </xpath>
<xpath expr="//tree//button[@name='button_pending']" position="replace"> <xpath expr="//tree//button[@name='button_pending']" position="replace">
@@ -584,6 +577,7 @@
<searchpanel> <searchpanel>
<field name="state" icon="fa-filter" enable_counters="1"/> <field name="state" icon="fa-filter" enable_counters="1"/>
<field name="delivery_status" icon="fa-filter" enable_counters="1"/> <field name="delivery_status" icon="fa-filter" enable_counters="1"/>
<field name="production_type" icon="fa-filter" enable_counters="1"/>
</searchpanel> </searchpanel>
</xpath> </xpath>
<filter name='todo' position="replace"/> <filter name='todo' position="replace"/>

View File

@@ -0,0 +1,18 @@
<odoo>
<data>
<record id="mrp_production_workorder_tree_editable_view_inherit" model="ir.ui.view">
<field name="name">>mrp.workorder.tree.editable.inherit</field>
<field name="model">mrp.workorder</field>
<field name="inherit_id" ref="mrp.mrp_production_workorder_tree_editable_view"/>
<field name="arch" type="xml">
<xpath expr="//tree/field[1]" position="before">
<header>
<button string="重新安排" name="%(sf_manufacturing.mrp_workorder_batch_replan_wizard)d" type="action"
class="treeHeaderBtn"
invisible="context.get('workorder_type') not in ('工件装夹中心','1#自动生产线','工件拆卸中心')"/>
</header>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@@ -29,6 +29,8 @@
</field> </field>
<field name="product_id" position="after"> <field name="product_id" position="after">
<field name="equipment_id" optional="hide"/> <field name="equipment_id" optional="hide"/>
<field name="part_name" optional="hide"/>
<field name="part_number" optional="show"/>
</field> </field>
<xpath expr="//field[@name='qty_remaining']" position="after"> <xpath expr="//field[@name='qty_remaining']" position="after">
<field name="manual_quotation" optional="show"/> <field name="manual_quotation" optional="show"/>
@@ -53,10 +55,7 @@
<!-- 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel')),--> <!-- 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel')),-->
<!-- ('is_user_working', '!=', False),("user_permissions","=",False),("name","=","CNC加工")]}--> <!-- ('is_user_working', '!=', False),("user_permissions","=",False),("name","=","CNC加工")]}-->
<!-- </attribute>--> <!-- </attribute>-->
<attribute name="attrs">{'invisible': ['|', '|', '|','|','|', ('production_state','in', ('draft', <attribute name="attrs">{'invisible': [('state', '!=', 'ready')]}
'done',
'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel')),
('is_user_working', '!=', False),("user_permissions","=",False),("name","in",("CNC加工","解除装夹"))]}
</attribute> </attribute>
</xpath> </xpath>
<xpath expr="//button[@name='%(mrp.act_mrp_block_workcenter_wo)d']" position="attributes"> <xpath expr="//button[@name='%(mrp.act_mrp_block_workcenter_wo)d']" position="attributes">
@@ -104,8 +103,7 @@
<!-- <field name="target">fullscreen</field>--> <!-- <field name="target">fullscreen</field>-->
<field name="target">current</field> <field name="target">current</field>
<field name="domain">[('state', '!=', 'cancel'),('schedule_state', '=', '已排')]</field> <field name="domain">[('state', '!=', 'cancel'),('schedule_state', '=', '已排')]</field>
<field name="context">{'search_default_product': 1, 'search_default_workcenter_id': <field name="context">{'search_default_product': 1, 'search_default_workcenter_id': active_id}
active_id,'search_default_filter_order_warning':1,'search_default_filter_order_overdue':1,'search_default_filter_order_normal':1}
</field> </field>
<field name="help" type="html"> <field name="help" type="html">
<p class="o_view_nocontent_workorder"> <p class="o_view_nocontent_workorder">
@@ -175,9 +173,7 @@
<!-- <button name="button_start" type="object" string="开始" class="btn-success" confirm="是否确认开始"--> <!-- <button name="button_start" type="object" string="开始" class="btn-success" confirm="是否确认开始"-->
<!-- attrs="{'invisible': ['|', '|', '|', ('production_state','in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel','to be detected')), ('is_user_working', '!=', False)]}"/>--> <!-- attrs="{'invisible': ['|', '|', '|', ('production_state','in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel','to be detected')), ('is_user_working', '!=', False)]}"/>-->
<button name="button_start" type="object" string="开始" class="btn-success" confirm="是否确认开始" <button name="button_start" type="object" string="开始" class="btn-success" confirm="是否确认开始"
attrs="{'invisible': ['|', '|', '|', '|', '|', ('routing_type', '=', '装夹预调'), ('routing_type', '=', '解除装夹'), ('production_state','in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel','to be detected')), ('is_user_working', '!=', False)]}"/> attrs="{'invisible': [('state', '!=', 'ready')]}"/>
<button name="button_start" type="object" string="开始" class="btn-success"
attrs="{'invisible': ['|', '|', '|', '|', ('routing_type', '!=', '装夹预调'), ('production_state','in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('state', 'in', ('done', 'cancel','to be detected')), ('is_user_working', '!=', False)]}"/>
<button name="button_pending" type="object" string="暂停" class="btn-warning" <button name="button_pending" type="object" string="暂停" class="btn-warning"
attrs="{'invisible': ['|', '|', ('production_state', 'in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('is_user_working', '=', False)]}"/> attrs="{'invisible': ['|', '|', ('production_state', 'in', ('draft', 'done', 'cancel')), ('working_state', '=', 'blocked'), ('is_user_working', '=', False)]}"/>
<button name="button_finish" type="object" string="完成" class="btn-success" confirm="是否确认完工" <button name="button_finish" type="object" string="完成" class="btn-success" confirm="是否确认完工"
@@ -200,7 +196,7 @@
<!-- attrs="{'invisible': ['|', '|', ('production_state', 'in', ('draft', 'done', 'cancel')), ('working_state', '!=', 'blocked'),('state','=','done')]}"/> --> <!-- attrs="{'invisible': ['|', '|', ('production_state', 'in', ('draft', 'done', 'cancel')), ('working_state', '!=', 'blocked'),('state','=','done')]}"/> -->
<!-- <button name="button_workpiece_delivery" type="object" string="工件配送" class="btn-primary"--> <!-- <button name="button_workpiece_delivery" type="object" string="工件配送" class="btn-primary"-->
<!-- attrs="{'invisible': ['|','|','|','|',('routing_type','!=','装夹预调'),('is_delivery','=',True),('state','!=','done'),('is_rework','=',True),'&amp;',('rfid_code','in',['',False]),('state','=','done')]}"/>--> <!-- attrs="{'invisible': ['|','|','|','|',('routing_type','!=','装夹预调'),('is_delivery','=',True),('state','!=','done'),('is_rework','=',True),'&amp;',('rfid_code','in',['',False]),('state','=','done')]}"/>-->
<button name="button_rework_pre" type="object" string="异常反馈" invisible="1" <button name="button_rework_pre" type="object" string="异常反馈"
class="btn-primary" class="btn-primary"
attrs="{'invisible': ['|','|',('routing_type','!=','装夹预调'),('state','!=','progress'),('is_rework','=',True)]}"/> attrs="{'invisible': ['|','|',('routing_type','!=','装夹预调'),('state','!=','progress'),('is_rework','=',True)]}"/>
<button name="unbind_tray" type="object" string="解绑托盘" <button name="unbind_tray" type="object" string="解绑托盘"

View File

@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record model="ir.ui.view" id="view_purchase_order_line_form_inherit_sf1">
<field name="name">purchase.order.form.inherit.sf</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='order_line']/tree/field[@name='name']" position="after">
<field name="related_product" optional="show"/>
<field name="part_number" optional="show"/>
</xpath>
<xpath expr="//sheet//div[@class='oe_button_box']" position="inside">
<button class="oe_stat_button" name="action_view_production" type="object" icon="fa-wrench"
attrs="{'invisible': [('production_count', '=', 0)]}"
>
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="production_count"/>
</span>
<span class="o_stat_text">制造订单</span>
</div>
</button>
</xpath>
<!-- 添加销售订单号字段-->
<xpath expr="//sheet/group/group[2]/div[@name='date_approve']" position="after">
<field name="origin_sale_id" readonly="1" string="参考销售订单"
attrs="{'invisible': [('origin_sale_id' , '=', False)]}"/>
<field name="origin_sale_ids" readonly="1" string="参考销售订单" widget="many2many_tags"
attrs="{'invisible': [('origin_sale_ids' , '=', [])]}"/>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@@ -13,11 +13,15 @@
<button name="action_confirm" string="供货方式确认" type="object" attrs="{'invisible': [('state', '!=', 'supply method')]}" confirm="确认供货方式"/> <button name="action_confirm" string="供货方式确认" type="object" attrs="{'invisible': [('state', '!=', 'supply method')]}" confirm="确认供货方式"/>
</xpath> </xpath>
<xpath expr="//header/field[@name='state']" position="attributes"> <xpath expr="//header/field[@name='state']" position="attributes">
<attribute name="statusbar_visible">draft,sent,supply method,sale</attribute> <attribute name="statusbar_visible">supply method,sale,processing,physical_distribution,delivered</attribute>
</xpath> </xpath>
<xpath expr="//page/field[@name='order_line']/tree/field[@name='remark']" position="before"> <xpath expr="//page/field[@name='order_line']/tree/field[@name='remark']" position="before">
<field name="supply_method" attrs="{'invisible': [('state', '=', 'draft')], 'required': [('state', '=', 'supply method')]}" /> <field name="supply_method" attrs="{'invisible': [('state', '=', 'draft')], 'required': [('state', '=', 'supply method')]}" />
</xpath> </xpath>
<xpath expr="//field[@name='order_line']/tree/field[@name='model_glb_file']" position="before">
<field name="part_number" optional="show"/>
</xpath>
<xpath expr="//header/button[@name='action_cancel']" position="attributes"> <xpath expr="//header/button[@name='action_cancel']" position="attributes">
<attribute name="attrs">{'invisible': [('state', '!=', 'draft')]}</attribute> <attribute name="attrs">{'invisible': [('state', '!=', 'draft')]}</attribute>
</xpath> </xpath>

View File

@@ -17,6 +17,12 @@
<field name="model">stock.picking</field> <field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/> <field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//field[@name='backorder_id']" position="after">
<field name="retrospect_ref"/>
</xpath>
<xpath expr="//field[@name='origin']" position="after">
<field name="sale_order_id"/>
</xpath>
<xpath expr="//field[@name='user_id']" position="after"> <xpath expr="//field[@name='user_id']" position="after">
<field name="picking_type_sequence_code" invisible="1"/> <field name="picking_type_sequence_code" invisible="1"/>
<field name="retrospect_ref" <field name="retrospect_ref"
@@ -26,6 +32,10 @@
attrs="{'invisible':[('picking_type_sequence_code','!=','DL')]}"/> attrs="{'invisible':[('picking_type_sequence_code','!=','DL')]}"/>
<field name="address_of_delivery" attrs="{'invisible':[('picking_type_sequence_code','!=','DL')]}"/> <field name="address_of_delivery" attrs="{'invisible':[('picking_type_sequence_code','!=','DL')]}"/>
</xpath> </xpath>
<xpath expr="//field[@name='product_id']" position="after">
<field name="part_number" optional="show"/>
<field name="part_name" optional="show"/>
</xpath>
</field> </field>
</record> </record>
@@ -34,6 +44,9 @@
<field name="model">stock.picking</field> <field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.vpicktree"/> <field name="inherit_id" ref="stock.vpicktree"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//field[@name='origin']" position="before">
<field name="sale_order_id" string="销售单号"/>
</xpath>
<xpath expr="//field[@name='origin']" position="after"> <xpath expr="//field[@name='origin']" position="after">
<field name="retrospect_ref"/> <field name="retrospect_ref"/>
</xpath> </xpath>
@@ -45,8 +58,13 @@
<field name="model">stock.picking</field> <field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_internal_search"/> <field name="inherit_id" ref="stock.view_picking_internal_search"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//field[@name='name']" position="replace">
<field name="name"/>
<field name="retrospect_ref"/>
<field name="sale_order_id" string="销售单号"/>
</xpath>
<xpath expr="//filter[@name='picking_type']" position="after"> <xpath expr="//filter[@name='picking_type']" position="after">
<filter string="追溯参考" name="retrospect_ref" domain="[]" <filter string="追溯参考" name="retrospect" domain="[]"
context="{'group_by': 'retrospect_ref'}"/> context="{'group_by': 'retrospect_ref'}"/>
</xpath> </xpath>
</field> </field>

View File

@@ -3,3 +3,4 @@ from . import rework_wizard
from . import production_wizard from . import production_wizard
from . import production_technology_wizard from . import production_technology_wizard
from . import production_technology_re_adjust_wizard from . import production_technology_re_adjust_wizard
from . import mrp_workorder_batch_replan_wizard

View File

@@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
import logging
from datetime import datetime, timedelta
from odoo import models, api, fields, _
class MrpWorkorderBatchReplanWizard(models.TransientModel):
_name = 'mrp.workorder.batch.replan.wizard'
_description = '制造订单批量重新安排向导'
def _get_date_planned_start(self):
planned_start_date = datetime.now() + timedelta(hours=2)
logging.info('计划开始加工时间: %s', planned_start_date)
return planned_start_date
def _get_default_workorder_count(self):
active_ids = self.env.context.get('active_ids', [])
return len(active_ids)
def _get_default_workorder_type(self):
active_ids = self.env.context.get('active_ids', [])
if active_ids:
workorders = self.env['mrp.workorder'].browse(active_ids)
if workorders:
routing_type = set(workorders.mapped('routing_type'))
return '/'.join(sorted(routing_type)) if routing_type else None
return None
workorder_type = fields.Char(string='工单类型', default=_get_default_workorder_type, readonly=True)
workorder_count = fields.Integer(string='工单数量',
default=_get_default_workorder_count,
readonly=True)
workorder_id = fields.Many2many('mrp.workorder', string=u'工单')
def confirm(self):
routing_type = set(self.workorder_id.mapped('routing_type'))
if len(routing_type) > 1:
raise models.ValidationError("批量重新安排工单类型必须一致。")
show_json_popover = self.workorder_id.mapped('show_json_popover')
if any(not value for value in show_json_popover):
raise models.ValidationError("所选工单必须都为逾期状态")
failed_workorders = {}
for workorder_info in self.workorder_id:
try:
workorder_info.action_replan()
except Exception as e:
reason = str(e)
if reason in failed_workorders:
failed_workorders[reason].append(
workorder_info.production_id.name)
else:
failed_workorders[reason] = [workorder_info.production_id.name]
if failed_workorders:
error_messages = "\n".join(
[f"制造订单: {', '.join(workorder_names)}, 原因: {reason}" for reason, workorder_names in
failed_workorders.items()])
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'message': f"以下工单重新安排失败:\n{error_messages}",
'sticky': False,
'color': 'red',
'next': {
'type': 'ir.actions.act_window_close'
}
},
}

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="mrp_workorder_batch_replan_wizard_form" model="ir.ui.view">
<field name="name">mrp.workorder.batch.replan.wizard.form.view</field>
<field name="model">mrp.workorder.batch.replan.wizard</field>
<field name="arch" type="xml">
<form>
<group>
<field name="workorder_type"/>
<field name="workorder_count"/>
</group>
<footer>
<button string="确认" name="confirm" type="object" class="oe_highlight"/>
<button string="取消" class="btn-primary" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="mrp_workorder_batch_replan_wizard" model="ir.actions.act_window">
<field name="name">重新安排工单</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mrp.workorder.batch.replan.wizard</field>
<field name="view_mode">form</field>
<field name="view_id" ref="mrp_workorder_batch_replan_wizard_form"/>
<field name="target">new</field>
<field name="context">{'default_workorder_id': active_ids}</field>
</record>
</odoo>

View File

@@ -10,7 +10,7 @@ class ProductionTechnologyReAdjustWizard(models.TransientModel):
production_id = fields.Many2one('mrp.production', string='制造订单号') production_id = fields.Many2one('mrp.production', string='制造订单号')
origin = fields.Char(string='源单据') origin = fields.Char(string='源单据')
is_technology_re_adjust = fields.Boolean(default=False) is_technology_re_adjust = fields.Boolean(default=True)
def confirm(self): def confirm(self):
if self.is_technology_re_adjust is True: if self.is_technology_re_adjust is True:
@@ -24,38 +24,7 @@ class ProductionTechnologyReAdjustWizard(models.TransientModel):
for production_item in productions: for production_item in productions:
# 该制造订单的其他同一销售订单的制造订单的工艺设计处理 # 该制造订单的其他同一销售订单的制造订单的工艺设计处理
if production_item != self.production_id: if production_item != self.production_id:
for td_other in production_item.technology_design_ids: self.env['sf.technology.design'].sudo().unified_procedure_multiple_work_orders(technology_designs, production_item)
if td_other.is_auto is False:
td_del = technology_designs.filtered(lambda tdo: tdo.route_id.id == td_other.route_id.id)
if not td_del or td_del.active is False:
td_other.write({'active': False})
for td_main in technology_designs:
route_other = production_item.technology_design_ids.filtered(
lambda td: td.route_id.id == td_main.route_id.id)
if not route_other and td_main.active is True:
production_item.write({'technology_design_ids': [(0, 0, {
'route_id': td_main.route_id.id,
'process_parameters_id': False if td_main.process_parameters_id is False else
self.env[
'sf.production.process.parameter'].search(
[('id', '=', td_main.process_parameters_id.id)]).id,
'sequence': td_main.sequence,
'is_auto': td_main.is_auto})]})
else:
for ro in route_other:
domain = [('production_id', '=', self.production_id.id),
('active', 'in', [True, False]),
('route_id', '=', ro.route_id.id)]
if ro.route_id.routing_type == '表面工艺':
domain += [('process_parameters_id', '=', ro.process_parameters_id.id)]
elif ro.route_id.routing_tag == 'special' and ro.is_auto is False:
# display_name = ro.route_id.display_name
domain += [('id', '=', ro.id)]
elif ro.panel is not False:
domain += [('panel', '=', ro.panel)]
td_upd = self.env['sf.technology.design'].sudo().search(domain)
if td_upd:
ro.write({'sequence': td_upd.sequence, 'active': td_upd.active})
special_design = self.env['sf.technology.design'].sudo().search( special_design = self.env['sf.technology.design'].sudo().search(
[('routing_tag', '=', 'special'), ('production_id', '=', production_item.id), [('routing_tag', '=', 'special'), ('production_id', '=', production_item.id),
('is_auto', '=', False), ('active', 'in', [True, False])]) ('is_auto', '=', False), ('active', 'in', [True, False])])
@@ -70,23 +39,6 @@ class ProductionTechnologyReAdjustWizard(models.TransientModel):
else: else:
domain += [('technology_design_id', '=', special.id), ('state', '!=', 'cancel')] domain += [('technology_design_id', '=', special.id), ('state', '!=', 'cancel')]
workorder = self.env['mrp.workorder'].search(domain) workorder = self.env['mrp.workorder'].search(domain)
# previous_workorder = self.env['mrp.workorder'].search(
# [('sequence', '=', workorder.sequence - 1), ('routing_type', '=', '表面工艺'),
# ('production_id', '=', workorder.production_id.id)])
# if previous_workorder:
# if previous_workorder.supplier_id != workorder.supplier_id:
# is_cancel = True
# else:
# is_cancel = True
# if workorder.state != 'cancel' and is_cancel is True:
# workorder.write({'state': 'cancel'})
# workorder.picking_ids.write({'state': 'cancel'})
# workorder.picking_ids.move_ids.write({'state': 'cancel'})
# purchase_order = self.env['purchase.order'].search(
# [('origin', '=', workorder.production_id.name)])
# for line in purchase_order.order_line:
# if line.product_id.server_product_process_parameters_id == workorder.surface_technics_parameters_id:
# purchase_order.write({'state': 'cancel'})
else: else:
workorder = self.env['mrp.workorder'].search( workorder = self.env['mrp.workorder'].search(
[('technology_design_id', '=', special.id), ('production_id', '=', special.production_id.id), ('state', '!=', 'cancel')]) [('technology_design_id', '=', special.id), ('production_id', '=', special.production_id.id), ('state', '!=', 'cancel')])
@@ -124,3 +76,4 @@ class ProductionTechnologyReAdjustWizard(models.TransientModel):
if workorders[ if workorders[
0].production_id.product_id.categ_id.type == '成品' and item.programming_state != '已编程': 0].production_id.product_id.categ_id.type == '成品' and item.programming_state != '已编程':
workorders[0].state = 'waiting' workorders[0].state = 'waiting'

View File

@@ -21,6 +21,26 @@
</field> </field>
</record> </record>
<record model="ir.ui.view" id="sf_production_technology_re_adjust_wizard_confirm_form_view">
<field name="name">sf.production.technology.re_adjust.wizard.form.view</field>
<field name="model">sf.production.technology.re_adjust.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<field name="production_id" invisible="1"/>
<field name="origin" invisible="1"/>
<div>
是否确认退回调整
</div>
<footer>
<button string="确认" name="confirm" type="object" class="oe_highlight"/>
<button string="取消" class="btn btn-secondary" special="cancel"/>
</footer>
</sheet>
</form>
</field>
</record>
<record id="action_sf_production_technology_re_adjust_wizard" model="ir.actions.act_window"> <record id="action_sf_production_technology_re_adjust_wizard" model="ir.actions.act_window">
<field name="name">工艺退回调整</field> <field name="name">工艺退回调整</field>
<field name="res_model">sf.production.technology.re_adjust.wizard</field> <field name="res_model">sf.production.technology.re_adjust.wizard</field>

View File

@@ -11,48 +11,23 @@ class ProductionTechnologyWizard(models.TransientModel):
production_id = fields.Many2one('mrp.production', string='制造订单号') production_id = fields.Many2one('mrp.production', string='制造订单号')
origin = fields.Char(string='源单据') origin = fields.Char(string='源单据')
is_technology_confirm = fields.Boolean(default=False) is_technology_confirm = fields.Boolean(default=True)
def confirm(self): def confirm(self):
if self.is_technology_confirm is True and self.production_id.product_id.categ_id.type == '成品': if self.is_technology_confirm is True and self.production_id.product_id.categ_id.type in ['成品', '坯料']:
domain = [('origin', '=', self.origin), ('state', '=', 'technology_to_confirmed'), domain = [('origin', '=', self.origin), ('state', '=', 'technology_to_confirmed'),
('product_id', '=', self.production_id.product_id.id)] ('product_id', '=', self.production_id.product_id.id)]
else: else:
domain = [('id', '=', self.production_id.id)] domain = [('id', '=', self.production_id.id)]
technology_designs = self.production_id.technology_design_ids technology_designs = self.env['sf.technology.design'].sudo().search(
[('production_id', '=', self.production_id.id), ('active', 'in', [True, False])])
# technology_designs = self.production_id.technology_design_ids
productions = self.env['mrp.production'].search(domain) productions = self.env['mrp.production'].search(domain)
for production in productions: for production in productions:
if production != self.production_id: if production != self.production_id:
for td_other in production.technology_design_ids: self.env['sf.technology.design'].sudo().unified_procedure_multiple_work_orders(technology_designs,
if td_other.is_auto is False: production)
td_del = technology_designs.filtered(lambda tdo: tdo.route_id.id == td_other.route_id.id) # 特殊表面工艺
if not td_del or td_del.active is False:
td_other.write({'active': False})
for td_main in technology_designs:
route_other = production.technology_design_ids.filtered(
lambda td: td.route_id.id == td_main.route_id.id)
if not route_other and td_main.active is True:
production.write({'technology_design_ids': [(0, 0, {
'route_id': td_main.route_id.id,
'process_parameters_id': False if td_main.process_parameters_id is False else self.env[
'sf.production.process.parameter'].search(
[('id', '=', td_main.process_parameters_id.id)]).id,
'sequence': td_main.sequence})]})
else:
for ro in route_other:
domain = [('production_id', '=', self.production_id.id),
('active', 'in', [True, False]),
('route_id', '=', ro.route_id.id)]
if ro.route_id.routing_type == '表面工艺':
domain += [('process_parameters_id', '=', ro.process_parameters_id.id)]
elif ro.route_id.routing_tag == 'special' and ro.is_auto is False:
# display_name = ro.route_id.display_name
domain += [('id', '=', ro.id)]
elif ro.panel is not False:
domain += [('panel', '=', ro.panel)]
td_upd = self.env['sf.technology.design'].sudo().search(domain)
if td_upd:
ro.write({'sequence': td_upd.sequence, 'active': td_upd.active})
special_design = self.env['sf.technology.design'].sudo().search( special_design = self.env['sf.technology.design'].sudo().search(
[('routing_tag', '=', 'special'), ('production_id', '=', production.id), [('routing_tag', '=', 'special'), ('production_id', '=', production.id),
('is_auto', '=', False), ('active', 'in', [True, False])]) ('is_auto', '=', False), ('active', 'in', [True, False])])
@@ -116,3 +91,4 @@ class ProductionTechnologyWizard(models.TransientModel):
if workorder[0].state in ['pending']: if workorder[0].state in ['pending']:
if workorder[0].production_id.product_id.categ_id.type == '成品' and item.programming_state != '已编程': if workorder[0].production_id.product_id.categ_id.type == '成品' and item.programming_state != '已编程':
workorder[0].state = 'waiting' workorder[0].state = 'waiting'
return productions

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<record model="ir.ui.view" id="sf_production_technology_wizard_form_view"> <record model="ir.ui.view" id="sf_production_technology_wizard_form_view">
<field name="name">sf.production.technology.wizard.form.view</field> <field name="name">sf.production.technology.wizard.form.view</field>
<field name="model">sf.production.technology.wizard</field> <field name="model">sf.production.technology.wizard</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
@@ -13,7 +13,28 @@
对当前制造订单,同一销售订单相同产品所生成的制造订单统一进行工艺调整与确认 对当前制造订单,同一销售订单相同产品所生成的制造订单统一进行工艺调整与确认
</div> </div>
<footer> <footer>
<button string="确认" name="confirm" type="object" class="oe_highlight" confirm="是否确认工艺调整"/> <button string="确认" name="confirm" type="object" class="oe_highlight"
confirm="是否确认工艺调整"/>
<button string="取消" class="btn btn-secondary" special="cancel"/>
</footer>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="sf_production_technology_wizard_confirm_form_view">
<field name="name">sf.production.technology.wizard.form.view</field>
<field name="model">sf.production.technology.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<field name="production_id" invisible="1"/>
<field name="origin" invisible="1"/>
<div>
是否确认工艺调整
</div>
<footer>
<button string="确认" name="confirm" type="object" class="oe_highlight"/>
<button string="取消" class="btn btn-secondary" special="cancel"/> <button string="取消" class="btn btn-secondary" special="cancel"/>
</footer> </footer>
</sheet> </sheet>

View File

@@ -62,6 +62,37 @@ class ReworkWizard(models.TransientModel):
hidden_workorder_list.sort() hidden_workorder_list.sort()
item.hidden_workorder_ids = ','.join(hidden_workorder_list) 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): def confirm(self):
if self.routing_type in ['装夹预调', 'CNC加工']: if self.routing_type in ['装夹预调', 'CNC加工']:
self.is_clamp_measure = False self.is_clamp_measure = False
@@ -79,36 +110,22 @@ class ReworkWizard(models.TransientModel):
if self.hidden_workorder_ids: if self.hidden_workorder_ids:
hidden_workorder_list = self.hidden_workorder_ids.split(',') hidden_workorder_list = self.hidden_workorder_ids.split(',')
rework_workorder_ids = self.workorder_ids.filtered(lambda w: str(w.id) in hidden_workorder_list) rework_workorder_ids = self.workorder_ids.filtered(lambda w: str(w.id) in hidden_workorder_list)
# 限制判断 # 调用效验方法
# 1、当制造订单内ZM面的工单都已完成时返工勾选工序时只能勾选上ZM面的所有工序进行返工 self.efficacy_rework_wo(rework_workorder_ids)
# 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
else: else:
raise ValidationError('请选择返工工单!!!') raise ValidationError('请选择返工工单!!!')
if rework_workorder_ids: if rework_workorder_ids:
clamp_workorder_ids = None # 限制
if rework_workorder_ids: # 1、单独返工CNC工单则不解绑托盘RFID如单独返工装夹预调工单则自动解绑托盘RFID
# 限制 # 2、返工CNC工单和装夹预调工单则自动解绑RFID
# 1、单独返工CNC工单则不解绑托盘RFID如单独返工装夹预调工单则自动解绑托盘RFID clamp_workorder_ids = rework_workorder_ids.filtered(lambda rp: rp.routing_type == '装夹预调')
# 2、返工CNC工单和装夹预调工单则自动解绑RFID if clamp_workorder_ids:
clamp_workorder_ids = rework_workorder_ids.filtered(lambda rp: rp.routing_type == '装夹预调') for clamp_workorder_id in clamp_workorder_ids:
if clamp_workorder_ids: self.production_id.workorder_ids.filtered(
for clamp_workorder_id in clamp_workorder_ids: lambda wk: wk.processing_panel == clamp_workorder_id.processing_panel).write(
self.production_id.workorder_ids.filtered( {'rfid_code': None})
lambda wk: wk.processing_panel == clamp_workorder_id.processing_panel).write( # 返工工单状态设置为【返工】
{'rfid_code': False}) rework_workorder_ids.write({'state': 'rework'})
# 返工工单状态设置为【返工】
rework_workorder_ids.write({'state': 'rework'})
# 查询返工工单对应的工艺设计记录,并调用方法拼接数据,用于创建新的工单 # 查询返工工单对应的工艺设计记录,并调用方法拼接数据,用于创建新的工单
workorders_values = [] workorders_values = []
for work in rework_workorder_ids: for work in rework_workorder_ids:
@@ -119,7 +136,7 @@ class ReworkWizard(models.TransientModel):
and item.panel == work.processing_panel)) and item.panel == work.processing_panel))
if route: if route:
work_list = self.env['mrp.workorder'].json_workorder_str(self.production_id, route[0]) 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) workorders_values.append(work_list)
# 创建新工单,并进行返工配置的相关操作 # 创建新工单,并进行返工配置的相关操作
if workorders_values: if workorders_values:
@@ -129,6 +146,11 @@ class ReworkWizard(models.TransientModel):
new_pre_workorder_ids = self.production_id.workorder_ids.filtered( new_pre_workorder_ids = self.production_id.workorder_ids.filtered(
lambda kw: kw.routing_type == '装夹预调' and kw.sequence == 0) lambda kw: kw.routing_type == '装夹预调' and kw.sequence == 0)
self.production_id._reset_work_order_sequence() 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( self.production_id.detection_result_ids.filtered(
lambda ap1: ap1.handle_result == '待处理').write({'handle_result': '已处理'}) lambda ap1: ap1.handle_result == '待处理').write({'handle_result': '已处理'})
panels = [] # 返工的加工面 panels = [] # 返工的加工面
@@ -249,11 +271,7 @@ class ReworkWizard(models.TransientModel):
elif self.programming_state in ['待编程', '编程中']: elif self.programming_state in ['待编程', '编程中']:
self.production_id.write( self.production_id.write(
{'programming_state': '编程中', 'work_state': '编程中', 'is_rework': True}) {'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': if self.production_id.state == 'progress':
self.production_id.write({'programming_state': '已编程', 'work_state': '已编程'}) self.production_id.write({'programming_state': '已编程', 'work_state': '已编程'})
if self.reprogramming_num >= 1 and self.programming_state == '已编程': if self.reprogramming_num >= 1 and self.programming_state == '已编程':
@@ -264,6 +282,13 @@ class ReworkWizard(models.TransientModel):
productions_not_delivered.write( productions_not_delivered.write(
{'state': 'progress', 'programming_state': '已编程', 'work_state': '已编程', {'state': 'progress', 'programming_state': '已编程', 'work_state': '已编程',
'is_rework': False}) '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') @api.onchange('production_id')
def onchange_processing_panel_id(self): def onchange_processing_panel_id(self):

View File

@@ -14,7 +14,7 @@
<field name="processing_panel_id" invisible="1"/> <field name="processing_panel_id" invisible="1"/>
<field name="hidden_workorder_ids" class="css_not_available_msg"/> <field name="hidden_workorder_ids" class="css_not_available_msg"/>
<group> <group>
<field name="hidden_workorder_ids"/> <field name="hidden_workorder_ids" invisible="1"/>
<field options="{'no_create': True,'no_open': True}" readonly="1" name="workorder_ids" <field options="{'no_create': True,'no_open': True}" readonly="1" name="workorder_ids"
widget="jikimo_subtree_selector_field" widget="jikimo_subtree_selector_field"
jikimo_selector="True" replace_context="hidden_workorder_ids" string="工序" jikimo_selector="True" replace_context="hidden_workorder_ids" string="工序"

View File

@@ -207,8 +207,9 @@ class WorkpieceDeliveryWizard(models.TransientModel):
workorder.production_line_id.id != self.production_ids[0].production_line_id.id): workorder.production_line_id.id != self.production_ids[0].production_line_id.id):
raise UserError(f'该rfid对应的制造订单号为{workorder.production_id.name}的目的生产线不一致') raise UserError(f'该rfid对应的制造订单号为{workorder.production_id.name}的目的生产线不一致')
# 调用打印成品条码方法 if workorder.routing_type == '解除装夹':
workorder.print_method() # 调用打印成品条码方法
workorder.print_method()
# 将对象添加到对应的同模型且是多对多类型里 # 将对象添加到对应的同模型且是多对多类型里
self.production_ids |= workorder.production_id self.production_ids |= workorder.production_id

View File

@@ -11,13 +11,16 @@
""", """,
'category': 'sf', 'category': 'sf',
'website': 'https://www.sf.jikimo.com', 'website': 'https://www.sf.jikimo.com',
'depends': ['sale', 'purchase', 'sf_plan', 'jikimo_message_notify', 'stock', 'sf_quality', 'mrp'], 'depends': ['sale', 'purchase', 'sf_plan', 'jikimo_message_notify', 'stock', 'sf_quality', 'mrp',
'sf_manufacturing', 'product', 'quality'],
'data': [ 'data': [
'data/bussiness_node.xml', 'data/bussiness_node.xml',
'data/cron_data.xml', 'data/cron_data.xml',
'data/template_data.xml', 'data/template_data.xml',
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'views/mrp_workorder_views.xml', 'views/mrp_workorder_views.xml',
'views/purchase_order_view.xml',
'views/stock_picking_view.xml',
], ],
'test': [ 'test': [
], ],

View File

@@ -4,10 +4,12 @@ import logging
from odoo import http from odoo import http
from odoo.http import request from odoo.http import request
from odoo.addons.sf_mrs_connect.controllers.controllers import Sf_Mrs_Connect from odoo.addons.sf_mrs_connect.controllers.controllers import Sf_Mrs_Connect
from odoo.addons.sf_bf_connect.controllers.controllers import Sf_Bf_Connect
from odoo.addons.sf_base.commons.common import Common from odoo.addons.sf_base.commons.common import Common
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
class MessageSfMrsConnect(Sf_Mrs_Connect): class MessageSfMrsConnect(Sf_Mrs_Connect):
@http.route('/api/cnc_processing/create', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, @http.route('/api/cnc_processing/create', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
@@ -39,7 +41,8 @@ class MessageSfMrsConnect(Sf_Mrs_Connect):
_logger.info('无效用刀异常消息推送接口:%s' % e) _logger.info('无效用刀异常消息推送接口:%s' % e)
return json.JSONEncoder().encode(res) return json.JSONEncoder().encode(res)
@http.route('/api/maintenance_logs/notify', type='json', auth='public', methods=['GET', 'POST'], csrf=False, cors="*") @http.route('/api/maintenance_logs/notify', type='json', auth='public', methods=['GET', 'POST'], csrf=False,
cors="*")
def maintenance_logs_notify(self, **kw): def maintenance_logs_notify(self, **kw):
res = {'code': 200, 'message': '设备故障日志信息推送成功'} res = {'code': 200, 'message': '设备故障日志信息推送成功'}
datas = request.httprequest.data datas = request.httprequest.data
@@ -51,10 +54,26 @@ class MessageSfMrsConnect(Sf_Mrs_Connect):
try: try:
if not isinstance(log_id, list): if not isinstance(log_id, list):
log_id = [log_id] log_id = [log_id]
maintenance_logs = request.env['sf.maintenance.logs'].sudo().search([('id', 'in', [int(id) for id in log_id])]) maintenance_logs = request.env['sf.maintenance.logs'].sudo().search(
[('id', 'in', [int(id) for id in log_id])])
if maintenance_logs: if maintenance_logs:
maintenance_logs.add_queue('设备故障') maintenance_logs.add_queue('设备故障')
except Exception as e: except Exception as e:
res = {'code': 400, 'message': '设备故障信息推送失败', 'error': str(e)} res = {'code': 400, 'message': '设备故障信息推送失败', 'error': str(e)}
return json.JSONEncoder().encode(res) return json.JSONEncoder().encode(res)
class MessageSfBfConnect(Sf_Bf_Connect):
@http.route('/api/bfm_process_order/list', type='http', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*")
def get_bfm_process_order_list(self, **kw):
res = super(MessageSfBfConnect, self).get_bfm_process_order_list(**kw)
response_data = json.loads(res.data.decode('utf-8'))
if response_data['status'] == 1:
try:
_logger.info('已进入待接单消息推送:%s' % response_data)
sale_order = request.env['sale.order'].sudo().search([('name', '=', response_data['factory_order_no'])])
sale_order.add_queue('待接单')
except Exception as e:
logging.info('add_queue error:%s' % e)
return res

View File

@@ -22,6 +22,15 @@
<field name="model">sale.order</field> <field name="model">sale.order</field>
</record> </record>
<record id="bussiness_supply_method" model="jikimo.message.bussiness.node">
<field name="name">待确认供货方式</field>
<field name="model">sale.order</field>
</record>
<record id="bussiness_technology_to_confirmed" model="jikimo.message.bussiness.node">
<field name="name">待确认加工路线</field>
<field name="model">mrp.production</field>
</record>
<record id="transfer_inventory" model="jikimo.message.bussiness.node"> <record id="transfer_inventory" model="jikimo.message.bussiness.node">
<field name="name">调拨入库</field> <field name="name">调拨入库</field>
@@ -47,7 +56,7 @@
<!--工单--> <!--工单-->
<record id="bussiness_mrp_workorder_remind" model="jikimo.message.bussiness.node"> <record id="bussiness_mrp_workorder_remind" model="jikimo.message.bussiness.node">
<field name="name">工单已下发通知</field> <field name="name">工单已下发通知</field>
<field name="model">mrp.workorder</field> <field name="model">product.product</field>
</record> </record>
<!--发货调拨--> <!--发货调拨-->
<record id="production_completed_warehouse_reminder" model="jikimo.message.bussiness.node"> <record id="production_completed_warehouse_reminder" model="jikimo.message.bussiness.node">
@@ -106,5 +115,45 @@
<field name="name">设备故障</field> <field name="name">设备故障</field>
<field name="model">sf.maintenance.logs</field> <field name="model">sf.maintenance.logs</field>
</record> </record>
<record id="bussiness_state_draft" model="jikimo.message.bussiness.node">
<field name="name">待排程</field>
<field name="model">mrp.production</field>
</record>
<record id="bussiness_outsourcing" model="jikimo.message.bussiness.node">
<field name="name">委外加工采购单提醒</field>
<field name="model">purchase.order</field>
</record>
<record id="bussiness_purchase" model="jikimo.message.bussiness.node">
<field name="name">外购订单采购单提醒</field>
<field name="model">purchase.order</field>
</record>
<record id="bussiness_process_outsourcing" model="jikimo.message.bussiness.node">
<field name="name">工序外协采购单通知</field>
<field name="model">purchase.order</field>
</record>
<record id="bussiness_outsourced_outbound" model="jikimo.message.bussiness.node">
<field name="name">工序外协发料通知</field>
<field name="model">mrp.production</field>
</record>
<record id="bussiness_purchase_order_warning" model="jikimo.message.bussiness.node">
<field name="name">采购订单预警提醒</field>
<field name="model">purchase.order</field>
</record>
<record id="bussiness_purchase_order_overdue" model="jikimo.message.bussiness.node">
<field name="name">采购单已逾期提醒</field>
<field name="model">purchase.order</field>
</record>
<record id="bussiness_quality_check" model="jikimo.message.bussiness.node">
<field name="name">待质检提醒</field>
<field name="model">product.product</field>
</record>
</data> </data>
</odoo> </odoo>

View File

@@ -10,7 +10,7 @@
<field name="msgtype">markdown</field> <field name="msgtype">markdown</field>
<field name="urgency">normal</field> <field name="urgency">normal</field>
<field name="content">### 待接单提醒: <field name="content">### 待接单提醒:
单号:销售订单[{{name}}]({{url}}) 单号:询价单[{{name}}]({{url}})
事项:请确认是否接单。 事项:请确认是否接单。
</field> </field>
</record> </record>
@@ -72,14 +72,14 @@
<field name="msgtype">markdown</field> <field name="msgtype">markdown</field>
<field name="urgency">normal</field> <field name="urgency">normal</field>
<field name="content">### 坯料发料提醒: <field name="content">### 坯料发料提醒:
单号:产品[{{product_id}}]({{request_url}}) 单号:发料单[{{name}}]({{request_url}})
事项:共{{number}}个生产发料单待确认处理</field> 事项:请确认坯料发料单并处理</field>
</record> </record>
<record id="template_mrp_workorder_remind" model="jikimo.message.template"> <record id="template_mrp_workorder_remind" model="jikimo.message.template">
<field name="name">工单已下发通知</field> <field name="name">工单已下发通知</field>
<field name="model_id" ref="mrp_workorder.model_mrp_workorder"/> <field name="model_id" ref="product.model_product_product"/>
<field name="model">mrp.workorder</field> <field name="model">product.product</field>
<field name="bussiness_node_id" ref="bussiness_mrp_workorder_remind"/> <field name="bussiness_node_id" ref="bussiness_mrp_workorder_remind"/>
<field name="msgtype">markdown</field> <field name="msgtype">markdown</field>
<field name="urgency">normal</field> <field name="urgency">normal</field>
@@ -275,5 +275,131 @@
机台号:[{{maintenance_equipment_id.name}}]({{url}}) 机台号:[{{maintenance_equipment_id.name}}]({{url}})
事项:{{create_date}}故障报警</field> 事项:{{create_date}}故障报警</field>
</record> </record>
<record id="template_supply_method" model="jikimo.message.template">
<field name="name">待确认供货方式</field>
<field name="model_id" ref="sale.model_sale_order"/>
<field name="model">sale.order</field>
<field name="bussiness_node_id" ref="bussiness_supply_method"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 待确认供货方式提醒:
单号:销售订单[{{name}}]({{url}})
事项:请确认供货方式。
</field>
</record>
<record id="template_technology_to_confirmed" model="jikimo.message.template">
<field name="name">待确认加工路线</field>
<field name="model_id" ref="mrp.model_mrp_production"/>
<field name="model">mrp.production</field>
<field name="bussiness_node_id" ref="bussiness_technology_to_confirmed"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 待确认加工路线提醒:
单号:产品[{{name}}]({{url}})
事项:有{{production_num}}个制造订单需要确认加工路线。
</field>
</record>
<record id="template_state_draft" model="jikimo.message.template">
<field name="name">待排程</field>
<field name="model_id" ref="mrp.model_mrp_production"/>
<field name="model">mrp.production</field>
<field name="bussiness_node_id" ref="bussiness_state_draft"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 待排程提醒:
单号:产品[{{name}}]({{url}})
事项:有{{production_num}}个制造订单待排程。
</field>
</record>
<record id="template_outsourcing_remind" model="jikimo.message.template">
<field name="name">委外加工采购单提醒</field>
<field name="model_id" ref="purchase.model_purchase_order"/>
<field name="model">purchase.order</field>
<field name="bussiness_node_id" ref="bussiness_outsourcing"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 委外加工采购通知:
单号:委外加工采购单[{{name}}]({{request_url}})
事项:请确认委外采购单并处理。</field>
</record>
<record id="template_purchase_remind" model="jikimo.message.template">
<field name="name">外购订单采购单提醒</field>
<field name="model_id" ref="purchase.model_purchase_order"/>
<field name="model">purchase.order</field>
<field name="bussiness_node_id" ref="bussiness_purchase"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 外购订单采购通知:
单号:外购采购单[{{name}}]({{request_url}})
事项:请确认外购采购单并处理。</field>
</record>
<record id="template_process_outsourcing_remind" model="jikimo.message.template">
<field name="name">工序外协采购单通知</field>
<field name="model_id" ref="purchase.model_purchase_order"/>
<field name="model">purchase.order</field>
<field name="bussiness_node_id" ref="bussiness_process_outsourcing"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 工序外协采购单通知:
单号:工序外协采购,产品[{{name}}]({{url}})
事项:请确认{{num}}个工序外协采购单并处理。</field>
</record>
<record id="template_outsourced_outbound_remind" model="jikimo.message.template">
<field name="name">工序外协发料通知</field>
<field name="model_id" ref="mrp.model_mrp_production"/>
<field name="model">mrp.production</field>
<field name="bussiness_node_id" ref="bussiness_outsourced_outbound"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 工序外协发料提醒:
单号:产品[{{name}}]({{url}})发料单
事项:请确认{{num}}个工序外协发料单并发料处理。</field>
</record>
<record id="template_purchase_order_warning" model="jikimo.message.template">
<field name="name">采购订单预警提醒</field>
<field name="model_id" ref="purchase.model_purchase_order"/>
<field name="model">purchase.order</field>
<field name="bussiness_node_id" ref="bussiness_purchase_order_warning"/>
<field name="msgtype">markdown</field>
<field name="send_type">timing</field>
<field name="urgency">normal</field>
<field name="content">### 采购订单预警提醒
事项:[共有{{num}}个采购订单有逾期风险]({{url}})
</field>
</record>
<record id="template_purchase_order_overdue" model="jikimo.message.template">
<field name="name">采购单已逾期提醒</field>
<field name="model_id" ref="purchase.model_purchase_order"/>
<field name="model">purchase.order</field>
<field name="bussiness_node_id" ref="bussiness_purchase_order_overdue"/>
<field name="msgtype">markdown</field>
<field name="send_type">timing</field>
<field name="urgency">normal</field>
<field name="content">### 采购单已逾期提醒
事项:[共有{{num}}个采购订单已逾期]({{url}})
</field>
</record>
<record id="template_quality_check" model="jikimo.message.template">
<field name="name">待质检提醒</field>
<field name="model_id" ref="product.model_product_product"/>
<field name="model">product.product</field>
<field name="bussiness_node_id" ref="bussiness_quality_check"/>
<field name="msgtype">markdown</field>
<field name="urgency">normal</field>
<field name="content">### 待质检提醒:
单号:产品[{{name}}]({{url}})
事项:有{{num}}个质检单需要处理。</field>
</record>
</data> </data>
</odoo> </odoo>

View File

@@ -10,3 +10,7 @@ from . import sf_message_functional_tool_dismantle
from . import sf_message_mrp_production from . import sf_message_mrp_production
from . import sf_message_quality_cnc_test from . import sf_message_quality_cnc_test
from . import sf_message_maintenance_logs 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

View File

@@ -13,11 +13,11 @@ class SFMessageMaintenanceLogs(models.Model):
return res return res
def _get_message(self, message_queue_ids): 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') url = self.env['ir.config_parameter'].get_param('web.base.url')
action_id = self.env.ref('sf_maintenance.action_maintenance_logs').id action_id = self.env.ref('sf_maintenance.action_maintenance_logs').id
for index, content in enumerate(contents): for index, content in enumerate(contents):
maintenance_logs_id = self.env['sf.maintenance.logs'].browse(message_queue_ids[index].res_id) 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) url = url + '/web#id=%s&view_type=form&action=%s' % (maintenance_logs_id.id, action_id)
contents[index] = content.replace('{{url}}', url) contents[index] = content.replace('{{url}}', url)
return contents return contents, message_queue_ids

View File

@@ -14,19 +14,24 @@ class SFMessageMrpProduction(models.Model):
'workorder_ids.state', 'product_qty', 'qty_producing') 'workorder_ids.state', 'product_qty', 'qty_producing')
def _compute_state(self): def _compute_state(self):
super(SFMessageMrpProduction, self)._compute_state() super(SFMessageMrpProduction, self)._compute_state()
for record in self: try:
if record.state in ['scrap', 'done']: 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'])]) mrp_production = record.env['mrp.production'].search(
if not mrp_production: [('origin', '=', record.origin), ('state', 'not in', ['scrap', 'done'])])
mrp_production_queue = self.env["jikimo.message.queue"].search([('res_id', '=', record.id)]) if not mrp_production:
if not mrp_production_queue: mrp_production_queue = self.env["jikimo.message.queue"].search([('res_id', '=', record.id)])
record.add_queue('生产完工入库提醒') 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): def _get_message(self, message_queue_ids):
contents = [] contents = []
unique_products = set()
technology_to_confirmed = set()
for message_queue_id in message_queue_ids: for message_queue_id in message_queue_ids:
if message_queue_id.message_template_id.name == '生产完工入库提醒': if message_queue_id.message_template_id.name == '生产完工入库提醒':
content = message_queue_id.message_template_id.content 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( content = content.replace('{{name}}', stock_picking_sfp.name).replace(
'{{sale_order_name}}', mrp_production.origin).replace('{{request_url}}', url) '{{sale_order_name}}', mrp_production.origin).replace('{{request_url}}', url)
contents.append(content) 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) 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 return contents
def request_url(self, id): def request_url(self, id):
@@ -48,7 +104,7 @@ class SFMessageMrpProduction(models.Model):
action_id = self.env.ref('stock.action_picking_tree_all').id action_id = self.env.ref('stock.action_picking_tree_all').id
menu_id = self.env['ir.model.data'].search([('name', '=', 'module_theme_treehouse')]).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'} 'view_type': 'form'}
# 拼接查询参数 # 拼接查询参数
query_string = urlencode(params) query_string = urlencode(params)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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

View File

@@ -8,20 +8,62 @@ class SFMessagePurchase(models.Model):
def _get_message(self, message_queue_ids): def _get_message(self, message_queue_ids):
contents = [] contents = []
process_outsourcing = set()
for message_queue_id in message_queue_ids: 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 content = message_queue_id.message_template_id.content
url = self.request_url(int(message_queue_id.res_id)) 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))]) purchase_order_line = self.env['purchase.order'].search([('id', '=', int(message_queue_id.res_id))])
content = content.replace('{{name}}', purchase_order_line.name).replace( content = content.replace('{{name}}', purchase_order_line.name).replace(
'{{request_url}}', url) '{{request_url}}', url)
contents.append(content) 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): def request_url(self, id):
url = self.env['ir.config_parameter'].get_param('web.base.url') url = self.env['ir.config_parameter'].get_param('web.base.url')
action_id = self.env.ref('purchase.purchase_form_action').id 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, params = {'id': id, 'menu_id': menu_id, 'action': action_id,
'model': 'purchase.order', 'model': 'purchase.order',
@@ -31,3 +73,27 @@ class SFMessagePurchase(models.Model):
# 拼接URL # 拼接URL
full_url = url + "/web#" + query_string full_url = url + "/web#" + query_string
return full_url 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

View File

@@ -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

View File

@@ -33,4 +33,4 @@ class SFMessageQualityCncTest(models.Model):
content_template = content.replace('{{judge_num}}', str(i)) content_template = content.replace('{{judge_num}}', str(i))
content_template = content_template.replace('{{url}}', url_with_id) content_template = content_template.replace('{{url}}', url_with_id)
contents.append(content_template) contents.append(content_template)
return contents return contents, message_queue_ids

View File

@@ -8,17 +8,6 @@ class SFMessageSale(models.Model):
_name = 'sale.order' _name = 'sale.order'
_inherit = ['sale.order', 'jikimo.message.dispatch'] _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): def action_confirm(self):
res = super(SFMessageSale, self).action_confirm() res = super(SFMessageSale, self).action_confirm()
@@ -29,19 +18,34 @@ class SFMessageSale(models.Model):
purchase_order_id = [] purchase_order_id = []
if picking_ids: if picking_ids:
for picking_id in picking_ids: for picking_id in picking_ids:
picking_id.add_queue('待确认加工路线')
purchase_order_ids = ( 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.created_purchase_line_id.order_id |
picking_id.procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id).ids picking_id.procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id).ids
purchase_order_id.extend(purchase_order_ids) purchase_order_id.extend(purchase_order_ids)
if purchase_order_id: 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: for purchase_order_info in purchase_order_list:
purchase_order_info.add_queue('坯料采购提醒') 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: except Exception as e:
logging.info('add_queue error:%s' % e) logging.info('add_queue error:%s' % e)
logging.info('action_confirm res:%s' % res) logging.info('action_confirm res:%s' % res)
return 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() # 继承并重写jikimo.message.dispatch的_get_message()
def _get_message(self, message_queue_ids): def _get_message(self, message_queue_ids):
contents = [] contents = []
@@ -53,14 +57,16 @@ class SFMessageSale(models.Model):
time_range = timedelta(minutes=2) time_range = timedelta(minutes=2)
i = 0 i = 0
for item in message_queue_ids: for item in message_queue_ids:
if item.message_template_id.bussiness_node_id.name == '待接单': if item.message_template_id.bussiness_node_id.name in ('待接单', '待确认供货方式'):
content = super(SFMessageSale, self)._get_message(item) content, _ = super(SFMessageSale, self)._get_message(item)
action_id = self.env.ref('sale.action_quotations_with_onboarding').id 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}" url_with_id = f"{url}/web#id={item.res_id}&view_type=form&action={action_id}"
content = content[0].replace('{{url}}', url_with_id) content = content[0].replace('{{url}}', url_with_id)
contents.append(content) contents.append(content)
elif item.message_template_id.bussiness_node_id.name == '确认接单': 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))]) 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...' % \ product = sale_order_line[0].product_id.name if len(sale_order_line) == 1 else '%s...' % \
sale_order_line[ sale_order_line[
@@ -97,7 +103,7 @@ class SFMessageSale(models.Model):
elif bussiness_node == '销售订单已逾期': elif bussiness_node == '销售订单已逾期':
content = content_template.replace('{{overdue_num}}', str(i)) content = content_template.replace('{{overdue_num}}', str(i))
contents.append(content) contents.append(content)
return contents return contents, message_queue_ids
# # 销售订单逾期预警和已逾期 # # 销售订单逾期预警和已逾期
def _overdue_or_warning_func(self): def _overdue_or_warning_func(self):

View File

@@ -12,26 +12,44 @@ class SFMessageStockPicking(models.Model):
@api.model_create_multi @api.model_create_multi
def create(self, vals): def create(self, vals):
result = super(SFMessageStockPicking, self).create(vals) result = super(SFMessageStockPicking, self).create(vals)
for obj in result: try:
if obj.location_id.name == '进货' and obj.location_dest_id.name == '刀具房': for obj in result:
obj.add_queue('调拨入库') 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 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')
def _compute_state(self): def _compute_state(self):
super(SFMessageStockPicking, self)._compute_state() super(SFMessageStockPicking, self)._compute_state()
for record in self: try:
if record.state == 'assigned' and record.check_in == 'PC': for record in self:
record.add_queue('坯料发料提醒') 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': if record.picking_type_id.sequence_code == 'SFP' and record.state == 'done':
stock_picking_sfp = record.env['stock.picking'].search( stock_picking_sfp = record.env['stock.picking'].search(
[('origin', '=', record.origin), ('state', '!=', 'done'), [('origin', '=', record.origin), ('state', '!=', 'done'),
('picking_type_id.sequence_code', '=', 'SFP')]) ('picking_type_id.sequence_code', '=', 'SFP')])
if not stock_picking_sfp: if not stock_picking_sfp:
stock_picking_send = self.env["jikimo.message.queue"].sudo().search([('res_id', '=', record.id)]) stock_picking_send = self.env["jikimo.message.queue"].sudo().search(
if not stock_picking_send: [('res_id', '=', record.id)])
record.add_queue('订单发货提醒') 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): # 处理订单发货提醒 def deal_stock_picking_sfp(self, message_queue_id): # 处理订单发货提醒
content = None content = None
@@ -49,32 +67,23 @@ class SFMessageStockPicking(models.Model):
def _get_message(self, message_queue_ids): def _get_message(self, message_queue_ids):
contents = [] contents = []
product_id = []
for message_queue_id in message_queue_ids: for message_queue_id in message_queue_ids:
i = 0
if message_queue_id.message_template_id.name == '坯料发料提醒': if message_queue_id.message_template_id.name == '坯料发料提醒':
content = message_queue_id.message_template_id.content content = message_queue_id.message_template_id.content
stock_picking_line = self.env['stock.picking'].sudo().search([('id', '=', int(message_queue_id.res_id))]) stock_picking_line = self.env['stock.picking'].sudo().search(
mrp_production_info = self.env['mrp.production'].sudo().search( [('id', '=', int(message_queue_id.res_id))])
[('name', '=', stock_picking_line.origin)]) url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
mrp_production_list = self.env['mrp.production'].sudo().search( action_id = self.env.ref('stock.action_picking_tree_ready').id
[('product_id', '=', mrp_production_info.product_id.id)]) menu_id = self.env.ref('stock.menu_stock_root').id
for mrp_production_line in mrp_production_list: url_with_id = f"{url}/web#view_type=form&action={action_id}&menu_id={menu_id}&id={stock_picking_line.id}"
picking_ids = mrp_production_line.picking_ids content = content.replace('{{name}}', stock_picking_line.name).replace(
for picking_id in picking_ids: '{{request_url}}', url_with_id)
if picking_id.state == 'assigned' and picking_id.check_in == 'PC': contents.append(content)
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)
elif message_queue_id.message_template_id.name == '订单发货提醒': elif message_queue_id.message_template_id.name == '订单发货提醒':
content = self.deal_stock_picking_sfp(message_queue_id) content = self.deal_stock_picking_sfp(message_queue_id)
if content: if content:
contents.append(content) contents.append(content)
return contents return contents, message_queue_ids
def get_special_url(self, id, tmplate_name, special_name, model_id): def get_special_url(self, id, tmplate_name, special_name, model_id):
menu_id = 0 menu_id = 0
@@ -86,19 +95,6 @@ class SFMessageStockPicking(models.Model):
else: else:
return super(SFMessageStockPicking, self).get_special_url(id, tmplate_name, special_name, model_id) 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): def request_url1(self, id):
url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
action_id = self.env.ref('stock.action_picking_tree_all').id action_id = self.env.ref('stock.action_picking_tree_all').id

View File

@@ -16,4 +16,6 @@ class SfMessageTemplate(models.Model):
res.append('mrp.workorder') res.append('mrp.workorder')
res.append('sf.maintenance.logs') res.append('sf.maintenance.logs')
res.append('quality.cnc.test') res.append('quality.cnc.test')
res.append('mrp.production')
res.append('product.product')
return res return res

View File

@@ -12,19 +12,30 @@ class SFMessageWork(models.Model):
_name = 'mrp.workorder' _name = 'mrp.workorder'
_inherit = ['mrp.workorder', 'jikimo.message.dispatch'] _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): def _compute_state(self):
super(SFMessageWork, self)._compute_state() super(SFMessageWork, self)._compute_state()
for workorder in self: 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( 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: if not jikimo_message_queue:
workorder.add_queue('工单已下发通知') workorder.production_id.product_id.add_queue('工单已下发通知')
def _get_message(self, message_queue_ids): def _get_message(self, message_queue_ids):
contents = [] contents = []
product_id = []
bussiness_node = None bussiness_node = None
url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') url = self.env['ir.config_parameter'].sudo().get_param('web.base.url')
current_time_strf = datetime.now().strftime("%Y-%m-%d %H:%M:%S") current_time_strf = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@@ -37,20 +48,7 @@ class SFMessageWork(models.Model):
} }
i = 0 i = 0
for message_queue_id in message_queue_ids: 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 template_names['预警'] + template_names['已逾期']:
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['已逾期']:
item = message_queue_id.message_template_id item = message_queue_id.message_template_id
bussiness_node = item.bussiness_node_id.name bussiness_node = item.bussiness_node_id.name
for reminder_time in item.reminder_time_ids: for reminder_time in item.reminder_time_ids:
@@ -93,21 +91,7 @@ class SFMessageWork(models.Model):
elif bussiness_node in template_names['已逾期']: elif bussiness_node in template_names['已逾期']:
content = content_template.replace('{{overdue_num}}', str(i)) content = content_template.replace('{{overdue_num}}', str(i))
contents.append(content) contents.append(content)
return contents return contents, message_queue_ids
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
def _overdue_or_warning_func(self): def _overdue_or_warning_func(self):
workorders = self.env['mrp.workorder'].search( workorders = self.env['mrp.workorder'].search(

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="purchase_form_warning_action" model="ir.actions.act_window">
<field name="name">采购订单</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">purchase.order</field>
<field name="view_mode">tree,kanban,form,pivot,graph,calendar,activity</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree', 'view_id': ref('purchase.purchase_order_view_tree')}),
(0, 0, {'view_mode': 'kanban', 'view_id': ref('purchase.purchase_order_view_kanban_without_dashboard')}),
]"/>
<field name="domain">[('state','in',('purchase', 'done'))]</field>
<field name="search_view_id" ref="purchase.purchase_order_view_search"/>
<field name="context">{'search_default_filter_order_warning':1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
还没有采购订单, 我们先创建一个!
</p>
<p>
当您下单到您的供应商,确定您的询价它会变成采购订单.
</p>
</field>
</record>
<record id="purchase_form_overdue_action" model="ir.actions.act_window">
<field name="name">采购订单</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">purchase.order</field>
<field name="view_mode">tree,kanban,form,pivot,graph,calendar,activity</field>
<field name="view_ids" eval="[(5, 0, 0),
(0, 0, {'view_mode': 'tree', 'view_id': ref('purchase.purchase_order_view_tree')}),
(0, 0, {'view_mode': 'kanban', 'view_id': ref('purchase.purchase_order_view_kanban_without_dashboard')}),
]"/>
<field name="domain">[('state','in',('purchase', 'done'))]</field>
<field name="search_view_id" ref="purchase.purchase_order_view_search"/>
<field name="context">{'search_default_filter_order_overdue':1}</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
还没有采购订单, 我们先创建一个!
</p>
<p>
当您下单到您的供应商,确定您的询价它会变成采购订单.
</p>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_picking_internal_search_sf" model="ir.ui.view">
<field name="name">stock.picking.search</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_internal_search"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='activities_exception']" position="before">
<filter name="outsourced" string="外协出库" domain="[('picking_type_id.name', '=', '外协出库')]"
invisible="1"/>
<separator/>
</xpath>
</field>
</record>
<record id="action_picking_outsourced_tree_ready" model="ir.actions.act_window">
<field name="name">待办</field>
<field name="res_model">stock.picking</field>
<field name="type">ir.actions.act_window</field>
<field name="view_mode">tree,kanban,form,calendar</field>
<field name="domain"></field>
<field name="context">{'search_default_outsourced': 1,'contact_display': 'partner_address',
'search_default_available': 1}
</field>
<field name="search_view_id" ref="stock.view_picking_internal_search"/>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">
没有仓库调拨。 让我们创建一个!
</p>
<p>
移动允许您将产品从一个位置移动到另外一个位置。
</p>
</field>
</record>
</odoo>

View File

@@ -26,7 +26,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
ret = json.loads(ret['result']) ret = json.loads(ret['result'])
logging.info('下发编程单:%s' % ret) logging.info('下发编程单:%s' % ret)
domain = [('programming_no', '=', ret['programming_no'])] 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'])] domain += [('state', 'not in', ['done', 'scrap', 'cancel'])]
else: else:
domain += [('state', 'in', ['confirmed', 'pending_cam'])] 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']) res['message'] = '编程单号为%s的CNC程序文件从FTP拉取失败' % (ret['programming_no'])
return json.JSONEncoder().encode(res) return json.JSONEncoder().encode(res)
for production in productions: 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(','): for panel in ret['processing_panel'].split(','):
# 查询状态为进行中且工序类型为CNC加工的工单 # 查询状态为进行中且工序类型为CNC加工的工单
cnc_workorder_has = production.workorder_ids.filtered( cnc_workorder_has = production.workorder_ids.filtered(

View File

@@ -13,7 +13,8 @@ class sf_production_plan(models.Model):
_description = 'sf_production_plan' _description = 'sf_production_plan'
_inherit = ['mail.thread'] _inherit = ['mail.thread']
# _order = 'state desc, write_date desc' # _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([ state = fields.Selection([
('draft', '待排程'), ('draft', '待排程'),
('done', '已排程'), ('done', '已排程'),

View File

@@ -17,6 +17,8 @@
decoration-danger="state == 'finished'"/> decoration-danger="state == 'finished'"/>
<field name="name"/> <field name="name"/>
<field name="origin"/> <field name="origin"/>
<field name="part_number" optional="show"/>
<field name="part_name" optional="hide"/>
<field name="order_deadline" widget="date"/> <field name="order_deadline" widget="date"/>
<field name="product_qty"/> <field name="product_qty"/>
<field name="production_line_id"/> <field name="production_line_id"/>

View File

@@ -2023,6 +2023,11 @@ msgstr "<span class=\"o_stat_text\">损失</span>"
msgid "<span class=\"o_stat_text\">Maintenance</span>" msgid "<span class=\"o_stat_text\">Maintenance</span>"
msgstr "" msgstr ""
#. module: sf_manufacturing
#: model:ir.model.fields.selection,name:sf_manufacturing.selection__mrp_production__state__confirmed
msgid "待排程"
msgstr "待排程"
#. module: mrp #. 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_product_form_view_bom_button
#: model_terms:ir.ui.view,arch_db:mrp.product_template_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 #: model_terms:ir.ui.view,arch_db:account.account_journal_dashboard_kanban_view
#, python-format #, python-format
msgid "Bills" msgid "Bills"
msgstr "账单" msgstr "发票账单"
#. module: account #. module: account
#: model_terms:ir.ui.view,arch_db:account.account_journal_dashboard_kanban_view #: 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 #: model_terms:ir.ui.view,arch_db:account.view_account_payment_search
#, python-format #, python-format
msgid "Payments" msgid "Payments"
msgstr "付" msgstr "付款单"
#. module: account #. module: account
#: model_terms:ir.actions.act_window,help:account.action_account_payments #: model_terms:ir.actions.act_window,help:account.action_account_payments

View File

@@ -16,7 +16,7 @@ class SfQualityCncTest(models.Model):
equipment_id = fields.Many2one(related='workorder_id.equipment_id', string='加工设备') equipment_id = fields.Many2one(related='workorder_id.equipment_id', string='加工设备')
production_line_id = fields.Many2one(related='workorder_id.production_line_id', production_line_id = fields.Many2one(related='workorder_id.production_line_id',
string='生产线') 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='检测报告') detection_report = fields.Binary(related='workorder_id.detection_report', readonly=True, string='检测报告')
state = fields.Selection([ state = fields.Selection([
('waiting', '待判定'), ('waiting', '待判定'),
@@ -25,7 +25,7 @@ class SfQualityCncTest(models.Model):
('pass', '合格'), ('pass', '合格'),
('fail', '不合格')], string='判定结果') ('fail', '不合格')], string='判定结果')
number = fields.Integer('数量', default=1) number = fields.Integer('数量', default=1)
test_results = fields.Selection([("合格", "合格")], string="检测结果") test_results = fields.Selection([("合格", "合格"), ("返工", "返工")], string="检测结果")
reason = fields.Selection( reason = fields.Selection(
[("programming", "编程"), ("cutter", "刀具"), ("clamping", "装夹"), ("operate computer", "操机"), [("programming", "编程"), ("cutter", "刀具"), ("clamping", "装夹"), ("operate computer", "操机"),
("technology", "工艺"), ("customer redrawing", "客户改图")], string="原因") ("technology", "工艺"), ("customer redrawing", "客户改图")], string="原因")

View File

@@ -18,7 +18,8 @@
<field name="production_id"/> <field name="production_id"/>
<field name="processing_panel"/> <field name="processing_panel"/>
<field name="product_id"/> <field name="product_id"/>
<field name="part_number"/> <field name="part_number" optional="show"/>
<field name="part_name" optional="hide"/>
<field name="number"/> <field name="number"/>
<field name="state" widget="badge" <field name="state" widget="badge"
decoration-success="state == 'done'" decoration-success="state == 'done'"

View File

@@ -16,6 +16,7 @@
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'wizard/sale_order_wizard_views.xml', 'wizard/sale_order_wizard_views.xml',
'wizard/purchase_order_wizard_views.xml', 'wizard/purchase_order_wizard_views.xml',
'data/cron_data.xml',
'views/sale_team.xml', 'views/sale_team.xml',
'views/sale_order_view.xml', 'views/sale_order_view.xml',
'views/res_partner_view.xml', 'views/res_partner_view.xml',
@@ -26,6 +27,7 @@
'assets': { 'assets': {
'web.assets_backend': [ 'web.assets_backend': [
'sf_sale/static/js/setTableWidth.js', 'sf_sale/static/js/setTableWidth.js',
'sf_sale/static/src/css/purchase_list.css',
] ]
}, },
'demo': [ 'demo': [

View File

@@ -0,0 +1,16 @@
<odoo>
<data noupdate="1">
<record model="ir.cron" id="ir_cron_purchase_order_overdue_warning">
<field name="name">检查采购单是否已逾期预警和逾期</field>
<field name="model_id" ref="model_purchase_order"/>
<field name="state">code</field>
<field name="code">model._overdue_or_warning_func()</field>
<field name="interval_number">10</field>
<field name="interval_type">minutes</field>
<field name="numbercall">-1</field>
<field name="doall" eval="False"/>
<field name="user_id" ref="base.user_root"/>
<field name="active" eval="True"/>
</record>
</data>
</odoo>

View File

@@ -55,12 +55,15 @@ class ReSaleOrder(models.Model):
store=True, readonly=False, copy=False, precompute=True, store=True, readonly=False, copy=False, precompute=True,
states=READONLY_FIELD_STATES, default=fields.Datetime.now) states=READONLY_FIELD_STATES, default=fields.Datetime.now)
delivery_warning = fields.Selection([('normal', '正常'), ('warning', '告警'), ('overdue', '逾期')], string='时效', delivery_warning = fields.Selection([('normal', '正常'), ('warning', '告警'), ('overdue', '逾期')],
tracking=True) default='normal',
string='时效', tracking=True)
order_code = fields.Char('平台订单号', readonly=True)
# 业务平台分配工厂后在智能工厂先创建销售订单 # 业务平台分配工厂后在智能工厂先创建销售订单
def sale_order_create(self, company_id, delivery_name, delivery_telephone, delivery_address, 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() now_time = datetime.datetime.now()
partner = self.get_customer() partner = self.get_customer()
data = { data = {
@@ -76,6 +79,7 @@ class ReSaleOrder(models.Model):
'address_of_delivery': delivery_address, 'address_of_delivery': delivery_address,
'payments_way': payments_way, 'payments_way': payments_way,
'pay_way': pay_way, 'pay_way': pay_way,
'order_code': order_number,
} }
if deadline_of_delivery: if deadline_of_delivery:
# deadline_of_delivery字段存在为false字符串情况 # deadline_of_delivery字段存在为false字符串情况
@@ -158,10 +162,71 @@ class ReSaleOrder(models.Model):
if not line.tax_id: if not line.tax_id:
raise UserError('请对【订单行】中的【税】进行选择') 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): class ResaleOrderLine(models.Model):
_inherit = 'sale.order.line' _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) model_glb_file = fields.Binary('模型的glb文件', compute='_compute_model_glb_file', store=True)
# product_template_id = fields.Many2one( # product_template_id = fields.Many2one(
# string="产品", # string="产品",
@@ -199,6 +264,7 @@ class ProductTemplate(models.Model):
_inherit = 'product.template' _inherit = 'product.template'
manual_quotation = fields.Boolean('人工编程', default=False) manual_quotation = fields.Boolean('人工编程', default=False)
part_name = fields.Char(string='零件名称', readonly=True)
class RePurchaseOrder(models.Model): class RePurchaseOrder(models.Model):
@@ -215,8 +281,37 @@ class RePurchaseOrder(models.Model):
compute='_compute_user_id', compute='_compute_user_id',
store=True) store=True)
purchase_type = fields.Selection([('standard', '标准采购'), ('consignment', '委外加工')], string='采购类型', purchase_type = fields.Selection(
default='standard') [('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') @api.depends('partner_id')
def _compute_user_id(self): def _compute_user_id(self):
@@ -252,21 +347,26 @@ class RePurchaseOrder(models.Model):
purchase_order = pp._get_surface_technics_purchase_ids() purchase_order = pp._get_surface_technics_purchase_ids()
if purchase_order: if purchase_order:
purchase_order.write({'state': 'draft'}) purchase_order.write({'state': 'draft'})
pp.purchase_id = [(6, 0, [purchase_order.id])]
else: else:
server_template = self.env['product.template'].search( server_template = self.env['product.template'].search(
[('server_product_process_parameters_id', '=', pp.surface_technics_parameters_id.id), [('server_product_process_parameters_id', '=', pp.surface_technics_parameters_id.id),
('detailed_type', '=', 'service')]) ('detailed_type', '=', 'service')])
server_product_process.append((0, 0, { server_product_process.append((0, 0, {
'product_id': server_template.product_variant_id.id, 'product_id': server_template.product_variant_id.id,
'product_qty': 1, 'product_qty': 1,
'product_uom': server_template.uom_id.id '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({ purchase_order = self.env['purchase.order'].sudo().create({
'partner_id': server_template.seller_ids[0].partner_id.id, 'partner_id': server_template.seller_ids[0].partner_id.id,
'origin': production.name, 'origin': production.name,
'state': 'draft', 'state': 'draft',
'purchase_type': 'consignment', '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])] pp.purchase_id = [(6, 0, [purchase_order.id])]
# self.env.cr.commit() # self.env.cr.commit()
@@ -291,14 +391,60 @@ class RePurchaseOrder(models.Model):
move_id.put_move_line() move_id.put_move_line()
for line in item.order_line: for line in item.order_line:
if line.product_id.categ_type == '表面工艺': if line.product_id.categ_type == '表面工艺':
for production_name in item.origin.split(','): if item.origin:
production = self.env['mrp.production'].search([('name', '=', production_name)]) for production_name in item.origin.split(','):
for workorder in production.workorder_ids.filtered( production = self.env['mrp.production'].search([('name', '=', production_name)])
lambda wd: wd.routing_type == '表面工艺' and wd.state == 'waiting' and line.product_id.server_product_process_parameters_id == wd.surface_technics_parameters_id): for workorder in production.workorder_ids.filtered(
workorder.state = 'ready' 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 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): class ResPartnerToSale(models.Model):
_inherit = 'res.partner' _inherit = 'res.partner'

View File

@@ -0,0 +1,3 @@
.purchase_order_list_name {
min-width: 62px !important;
}

View File

@@ -9,41 +9,39 @@
<xpath expr="//header/button[@name='action_view_picking']" position="attributes"> <xpath expr="//header/button[@name='action_view_picking']" position="attributes">
<attribute name="invisible">1</attribute> <attribute name="invisible">1</attribute>
</xpath> </xpath>
<xpath expr="//header/button[@name='button_confirm']" position="replace">
</xpath>
<xpath expr="//header/button[@name='button_confirm']" position="replace">
</xpath>
<xpath expr="//header/button[@name='action_rfq_send'][1]" position="before"> <xpath expr="//header/button[@name='action_rfq_send'][1]" position="before">
<button name="button_confirm" type="object" states="sent" string="Confirm Order" <button name="button_confirm" type="object" string="确认订单"
context="{'validate_analytic': True}" class="oe_highlight" id="bid_confirm" context="{'validate_analytic': True}" class="oe_highlight" id="draft_confirm"
data-hotkey="v"/> groups="sf_base.group_purchase,sf_base.group_purchase_director"
<button name="button_confirm" type="object" states="draft" context="{'validate_analytic': True}" attrs="{'invisible': [('state', 'in', ['purchase', 'done', 'cancel'])]}"
string="Confirm Order" id="draft_confirm"/> />
<button name="action_view_picking" <button name="action_view_picking"
string="接收产品" class="oe_highlight" type="object" string="接收产品" class="oe_highlight" type="object"
attrs="{'invisible': ['|', '|' , ('is_shipped', '=', True), ('state','not in', ('purchase','done')), ('incoming_picking_count', '=', 0)]}" attrs="{'invisible': ['|', '|' , ('is_shipped', '=', True), ('state','not in', ('purchase','done')), ('incoming_picking_count', '=', 0)]}"
data-hotkey="y" groups="stock.group_stock_user"/> data-hotkey="y" groups="stock.group_stock_user"/>
<button name="button_cancel" states="draft,to approve,sent,purchase" string="取消" type="object" data-hotkey="x" /> <button name="button_cancel" states="draft,to approve,sent,purchase" string="取消" type="object"
data-hotkey="x"/>
</xpath> </xpath>
<xpath expr="//header/button[@name='button_cancel'][2]" position="attributes"> <xpath expr="//header/button[@name='button_cancel'][2]" position="attributes">
<attribute name="invisible">1</attribute> <attribute name="invisible">1</attribute>
</xpath> </xpath>
<xpath expr="//header/button[@name='button_confirm'][3]" position="attributes"> <xpath expr="//field[@name='user_id']" position="attributes">
<attribute name="invisible">1</attribute> <attribute name="string">采购员</attribute>
</xpath> </xpath>
<xpath expr="//header/button[@name='button_confirm'][4]" position="attributes"> <xpath expr="//field[@name='origin']" position="attributes">
<attribute name="invisible">1</attribute> <attribute name="string">源单据</attribute>
</xpath> </xpath>
<field name="partner_id" position="replace"> <field name="partner_id" position="replace">
<field name="partner_id" widget="res_partner_many2one" context="{'is_supplier': True }"/> <field name="partner_id" widget="res_partner_many2one" context="{'is_supplier': True }"/>
</field> </field>
<field name="currency_id" position="after"> <field name="currency_id" position="after">
<field name="remark" attrs="{'readonly': [('state', 'in', ['purchase'])]}"/> <field name="remark" attrs="{'readonly': [('state', 'in', ['purchase'])]}" string="订单备注"/>
</field> </field>
<xpath expr="//form/header/button[@name='button_confirm'][2]" position="replace">
<button name="button_confirm" type="object" context="{'validate_analytic': True}"
string="确认订单" id="draft_confirm"
groups="sf_base.group_purchase,sf_base.group_purchase_director"
attrs="{'invisible': [('state', 'in', ['purchase'])]}"
/>
</xpath>
<xpath expr="//form/header/button[@name='action_rfq_send'][1]" position="replace"> <xpath expr="//form/header/button[@name='action_rfq_send'][1]" position="replace">
<button name="action_rfq_send" states="draft" string="通过Email发送采购单" type="object" <button name="action_rfq_send" states="draft" string="通过Email发送采购单" type="object"
context="{'send_rfq':True}" class="oe_highlight" data-hotkey="g" context="{'send_rfq':True}" class="oe_highlight" data-hotkey="g"
@@ -80,10 +78,6 @@
<xpath expr="//form/header/button[@name='action_create_invoice'][2]" position="attributes"> <xpath expr="//form/header/button[@name='action_create_invoice'][2]" position="attributes">
<attribute name="groups">sf_base.group_purchase,sf_base.group_purchase_director</attribute> <attribute name="groups">sf_base.group_purchase,sf_base.group_purchase_director</attribute>
</xpath> </xpath>
<xpath expr="//form/header/button[@name='button_confirm']" position="attributes">
<attribute name="groups">sf_base.group_purchase,sf_base.group_purchase_director</attribute>
<!-- <button name="action_create_invoice" string="创建账单" type="object" class="oe_highlight" context="{'create_bill':True}" attrs="{'invisible': ['|', ('state', 'not in', ('purchase', 'done')), ('invoice_status', 'in', ('no', 'invoiced'))]}" data-hotkey="w" groups="sf_base.group_purchase,sf_base.group_purchase_director"/> -->
</xpath>
<!-- <xpath expr="//form/header/button[@name='action_create_invoice[2]']" position="attributes">--> <!-- <xpath expr="//form/header/button[@name='action_create_invoice[2]']" position="attributes">-->
<!-- <attribute name="groups">sf_base.group_purchase,sf_base.group_purchase_director</attribute>--> <!-- <attribute name="groups">sf_base.group_purchase,sf_base.group_purchase_director</attribute>-->
<!-- </xpath>--> <!-- </xpath>-->
@@ -130,6 +124,23 @@
<xpath expr="//field[@name='order_line']/tree/field[@name='product_id']" position="attributes"> <xpath expr="//field[@name='order_line']/tree/field[@name='product_id']" position="attributes">
<attribute name="options">{'no_create': True}</attribute> <attribute name="options">{'no_create': True}</attribute>
</xpath> </xpath>
<xpath expr="//field[@name='order_line']/tree/field[@name='name']" position="attributes">
<attribute name="optional">hide</attribute>
</xpath>
<xpath expr="//field[@name='order_line']/tree/field[@name='name']" position="after">
<field name="part_name" optional="show"/>
<!-- <field name="part_number" optional="show"/>-->
</xpath>
<xpath expr="//field[@name='date_order']" position="attributes">
<attribute name="string">签约日期</attribute>
</xpath>
<field name="payment_term_id" position="attributes">
<attribute name="invisible">1</attribute>
</field>
<xpath expr="//field[@name='date_order']" position="after">
<field name="payment_term_id" attrs="{'readonly': ['|', ('invoice_status','=', 'invoiced'), ('state', '=', 'done')]}" options="{'no_create': True}"/>
<field name="contract_summary"/>
</xpath>
<field name="partner_ref" position="attributes"> <field name="partner_ref" position="attributes">
<attribute name="attrs">{'readonly': [('state', 'in', ['purchase'])]} <attribute name="attrs">{'readonly': [('state', 'in', ['purchase'])]}
</attribute> </attribute>
@@ -159,10 +170,46 @@
</attribute> </attribute>
</field> </field>
<!-- 添加采购类型字段 --> <!-- 添加采购类型字段 -->
<field name="partner_ref" position="after"> <field name="partner_id" position="after">
<field name="purchase_type" string="采购类型" readonly="1"/> <field name="purchase_type" string="采购类型" readonly="1"/>
<field name="picking_type_id" string="作业类型" domain="[('code','=','incoming'), '|', ('warehouse_id', '=', False), ('warehouse_id.company_id', '=', company_id)]" options="{'no_create': True}" groups="stock.group_stock_multi_locations"/>
<field name="urgent_purchase"/>
<label for="date_planned" string="最近交货日期"/>
<div name="date_planned_div" class="o_row">
<field name="date_planned" attrs="{'readonly': [('state', 'not in', ('draft', 'sent', 'to approve', 'purchase'))]}"/>
<field name="mail_reminder_confirmed" invisible="1"/>
<span class="text-muted" attrs="{'invisible': [('mail_reminder_confirmed', '=', False)]}">(confirmed by vendor)</span>
</div>
<field name="effective_date" attrs="{'invisible': [('effective_date', '=', False)]}" string="到货时间"/>
</field> </field>
<field name="partner_ref" position="attributes">
<attribute name="invisible">1</attribute>
</field>
<xpath expr="//sheet/group/group[2]/field[1]" position="before">
<field name="partner_ref" string="合同名称"/>
<field name="contract_number"/>
</xpath>
<xpath expr="//sheet/group/group[2]/field[@name='effective_date']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<!-- 隐藏 label -->
<xpath expr="//label[@for='receipt_reminder_email']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<!-- 隐藏整个 reminder div -->
<xpath expr="//div[@name='reminder']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<!-- 使用更精确的路径定位原始元素 -->
<xpath expr="//sheet/group/group[2]/label[@for='date_planned']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//sheet/group/group[2]/div[@name='date_planned_div']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
</field> </field>
</record> </record>
@@ -174,6 +221,30 @@
<xpath expr="//tree//header//button[@name='action_create_invoice']" position="attributes"> <xpath expr="//tree//header//button[@name='action_create_invoice']" position="attributes">
<attribute name="groups">sf_base.group_purchase,sf_base.group_purchase_director</attribute> <attribute name="groups">sf_base.group_purchase,sf_base.group_purchase_director</attribute>
</xpath> </xpath>
<xpath expr="//field[@name='user_id']" position="attributes">
<attribute name="string">采购员</attribute>
</xpath>
<xpath expr="//field[@name='origin']" position="attributes">
<attribute name="string">源单据</attribute>
</xpath>
<xpath expr="//field[@name='activity_ids']" position="attributes">
<attribute name="optional">hide</attribute>
</xpath>
<xpath expr="//field[@name='date_order']" position="attributes">
<attribute name="string">签约日期</attribute>
<attribute name="widget">''</attribute>
</xpath>
<xpath expr="//field[@name='date_planned']" position="replace">
</xpath>
<xpath expr="//field[@name='date_order']" position="after">
<field name="date_planned" string="最近交货日期"/>
</xpath>
<xpath expr="//field[@name='name']" position="after">
<field name="purchase_type"/>
</xpath>
<xpath expr="//field[@name='name']" position="attributes">
<attribute name="class">purchase_order_list_name</attribute>
</xpath>
</field> </field>
</record> </record>
@@ -190,6 +261,8 @@
<attribute name="attrs">{'readonly': [('state', 'in', ['purchase'])]} <attribute name="attrs">{'readonly': [('state', 'in', ['purchase'])]}
</attribute> </attribute>
</field> </field>
<xpath expr="//field[@name='picking_type_id'][1]" position="replace">
</xpath>
</field> </field>
</record> </record>
@@ -200,7 +273,46 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree position="attributes"> <tree position="attributes">
<attribute name="default_order">date_approve asc</attribute> <attribute name="default_order">date_approve asc</attribute>
<attribute name="decoration-warning">delivery_warning == 'warning'</attribute>
<attribute name="decoration-danger">delivery_warning == 'overdue'</attribute>
</tree> </tree>
<xpath expr="//field[@name='activity_ids']" position="attributes">
<attribute name="optional">hide</attribute>
</xpath>
<xpath expr="//field[@name='date_planned']" position="replace">
</xpath>
<xpath expr="//field[@name='user_id']" position="attributes">
<attribute name="string">采购员</attribute>
</xpath>
<xpath expr="//field[@name='origin']" position="attributes">
<attribute name="string">源单据</attribute>
</xpath>
<xpath expr="//field[@name='user_id']" position="after">
<field name="delivery_warning" optional="show"/>
<field name="date_planned" string="最近交货日期" optional="show" widget="date"/>
</xpath>
<xpath expr="//field[@name='name']" position="after">
<field name="purchase_type"/>
<field name="delivery_warning" invisible="1"/>
</xpath>
<xpath expr="//field[@name='name']" position="attributes">
<attribute name="class">purchase_order_list_name</attribute>
</xpath>
</field>
</record>
<record id="view_purchase_order_view_search_inherit_sf" model="ir.ui.view">
<field name="name">purchase.order.search.inherit.sf</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_view_search"/>
<field name="arch" type="xml">
<xpath expr="//filter[@name='order_date']" position="after">
<separator/>
<filter string="正常" name="filter_order_normal"
domain="['|', ('delivery_warning', '=', 'normal'), ('delivery_warning', '=', False)]"/>
<filter string="预警" name="filter_order_warning" domain="[('delivery_warning', '=', 'warning')]"/>
<filter string="逾期" name="filter_order_overdue" domain="[('delivery_warning', '=', 'overdue')]"/>
</xpath>
</field> </field>
</record> </record>
@@ -212,8 +324,29 @@
<xpath expr="//field[@name='name']" position="replace"> <xpath expr="//field[@name='name']" position="replace">
<field name="name" string="单据编码" filter_domain="[('name', 'ilike', self)]"/> <field name="name" string="单据编码" filter_domain="[('name', 'ilike', self)]"/>
</xpath> </xpath>
<!-- <xpath expr="//search" position="inside">-->
<!-- <searchpanel>-->
<!-- <field name="purchase_type" icon="fa-filter"/>-->
<!-- <field name="state" icon="fa-filter"/>-->
<!-- </searchpanel>-->
<!-- </xpath>-->
</field> </field>
</record> </record>
<record id="purchase_order_view_search_sf" model="ir.ui.view">
<field name="name">purchase.order.list.select.sf</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="purchase.purchase_order_view_search"/>
<field name="arch" type="xml">
<xpath expr="//search" position="inside">
<searchpanel>
<field name="purchase_type" icon="fa-filter" enable_counters="1"/>
<field name="delivery_warning" icon="fa-filter" enable_counters="1"/>
</searchpanel>
</xpath>
</field>
</record>
<record id="purchase.product_normal_action_puchased" model="ir.actions.act_window"> <record id="purchase.product_normal_action_puchased" model="ir.actions.act_window">
<field name="context"> <field name="context">
{"search_default_categ_id":1,"search_default_filter_to_purchase":1, "purchase_product_template": 1} {"search_default_categ_id":1,"search_default_filter_to_purchase":1, "purchase_product_template": 1}

View File

@@ -103,6 +103,7 @@
<xpath expr="//field[@name='order_line']/tree/field[@name='name']" position="before"> <xpath expr="//field[@name='order_line']/tree/field[@name='name']" position="before">
<field name="model_glb_file" widget="Viewer3D" optional="show" <field name="model_glb_file" widget="Viewer3D" optional="show"
string="模型文件" attrs="{'readonly': [('state', 'in', ['draft'])]}"/> string="模型文件" attrs="{'readonly': [('state', 'in', ['draft'])]}"/>
<field name="part_name" optional="hide"/>
</xpath> </xpath>
<xpath expr="//field[@name='order_line']/tree/field[@name='price_subtotal']" position="after"> <xpath expr="//field[@name='order_line']/tree/field[@name='price_subtotal']" position="after">
<field name="remark"/> <field name="remark"/>
@@ -169,17 +170,42 @@
<field name="manual_quotation" /> <field name="manual_quotation" />
<field name="is_incoming_material"/> <field name="is_incoming_material"/>
</xpath> </xpath>
<xpath expr="//div[@class='o_td_label'][2]" position="replace"></xpath>
<xpath expr="//field[@name='date_order'][1]" position="replace"></xpath>
<xpath expr="//field[@name='date_order']" position="replace"></xpath>
<xpath expr="//div[@class='o_td_label'][1]" position="replace">
<div class="o_td_label" attrs="{'invisible': [('state', 'in', ['done', 'cancel'])]}">
<label for="date_order" string="下单日期"/>
</div>
<field name="date_order" attrs="{'invisible': [('state', 'in', ['done', 'cancel'])], 'required': True}" nolabel="1"/>
</xpath>
</field> </field>
</record> </record>
<record id="sale_order_inherited_form_purchase_sf" model="ir.ui.view">
<field name="name">sale.order.inherited.form.purchase</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale_purchase.sale_order_inherited_form_purchase"/>
<field name="arch" type="xml">
<xpath expr="//button[@name='action_view_purchase_orders']" position="before">
<button class="oe_stat_button" name="action_view_consignment_purchase_orders" type="object" icon="fa-credit-card"
groups='purchase.group_purchase_user'
attrs="{'invisible': [('consignment_purchase_order_count', '=', 0)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="consignment_purchase_order_count"/></span>
<span class="o_stat_text">委外加工</span>
</div>
</button>
</xpath>
</field>
</record>
<record model="ir.ui.view" id="view_sale_management_order_form_quote_inherit_sf"> <record model="ir.ui.view" id="view_sale_management_order_form_quote_inherit_sf">
<field name="name">sale.management.order.form.quote.inherit.sf</field> <field name="name">sale.management.order.form.quote.inherit.sf</field>
<field name="model">sale.order</field> <field name="model">sale.order</field>
<field name="inherit_id" ref="sale_management.sale_order_form_quote"/> <field name="inherit_id" ref="sale_management.sale_order_form_quote"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="date_order" position="attributes">
<attribute name="string">下单日期</attribute>
</field>
<field name="sale_order_template_id" position="after"> <field name="sale_order_template_id" position="after">
<field name="remark" attrs="{'readonly': [('state', 'in', ('sale','cancel'))]}"/> <field name="remark" attrs="{'readonly': [('state', 'in', ('sale','cancel'))]}"/>
</field> </field>
@@ -215,8 +241,15 @@
<attribute name="string">订单号</attribute> <attribute name="string">订单号</attribute>
</field> </field>
<field name="create_date" position="attributes"> <field name="create_date" position="attributes">
<attribute name="string">下单时间</attribute> <attribute name="string">下单日期</attribute>
</field> </field>
<xpath expr="//field[@name='activity_ids']" position="attributes">
<attribute name="optional">hide</attribute>
</xpath>
<xpath expr="//field[@name='create_date']" position="after">
<field name="order_code"/>
<field name="deadline_of_delivery"/>
</xpath>
<!-- <field name="state" position="after"> <!-- <field name="state" position="after">
<field name="check_status" widget="badge" <field name="check_status" widget="badge"
decoration-success="check_status == 'approved'" decoration-success="check_status == 'approved'"
@@ -236,6 +269,10 @@
<separator/> <separator/>
<filter string="预警" name="filter_order_warning" domain="[('delivery_warning', '=', 'warning')]"/> <filter string="预警" name="filter_order_warning" domain="[('delivery_warning', '=', 'warning')]"/>
<filter string="逾期" name="filter_order_overdue" domain="[('delivery_warning', '=', 'overdue')]"/> <filter string="逾期" name="filter_order_overdue" domain="[('delivery_warning', '=', 'overdue')]"/>
<searchpanel>
<field name="delivery_warning" string="交期状态" icon="fa-filter" enable_counters="1"/>
<field name="state" icon="fa-filter" enable_counters="1"/>
</searchpanel>
</xpath> </xpath>
</field> </field>
</record> </record>
@@ -258,6 +295,22 @@
<field name="amount_total" position="after"> <field name="amount_total" position="after">
<field name="delivery_warning" invisible="1"/> <field name="delivery_warning" invisible="1"/>
</field> </field>
<field name="date_order" position="attributes">
<attribute name="string">下单日期</attribute>
</field>
<xpath expr="//field[@name='date_order']" position="after">
<field name="order_code"/>
<field name="deadline_of_delivery"/>
</xpath>
<xpath expr="//field[@name='activity_ids']" position="attributes">
<attribute name="optional">hide</attribute>
</xpath>
<xpath expr="//field[@name='invoice_status']" position="after">
<field name="state"/>
</xpath>
<xpath expr="//field[@name='user_id']" position="after">
<field name="delivery_warning" string="交期状态"/>
</xpath>
</field> </field>
</record> </record>
@@ -293,7 +346,7 @@
<record id="sale.action_orders" model="ir.actions.act_window"> <record id="sale.action_orders" model="ir.actions.act_window">
<field name="search_view_id" ref="sale_order_view_search_inherit_sale_message"/> <field name="search_view_id" ref="sale_order_view_search_inherit_sale_message"/>
<field name="context">{ 'search_default_filter_order_warning':1,'search_default_filter_order_overdue':1} <field name="context">{}
</field> </field>
</record> </record>
</data> </data>

View File

@@ -13,6 +13,74 @@ _logger = logging.getLogger(__name__)
class StockPicking(models.Model): class StockPicking(models.Model):
_inherit = 'stock.picking' _inherit = 'stock.picking'
pro_purchase_count = fields.Integer('坯料采购单数量', compute='_compute_pro_purchase_count', store=True)
@api.depends('name')
def _compute_pro_purchase_count(self):
for sp in self:
if sp:
po_ids = self.env['purchase.order'].sudo().search([
('origin', 'like', sp.name), ('purchase_type', '=', 'standard')])
if po_ids:
sp.pro_purchase_count = len(po_ids)
def pro_purchase_order(self):
"""
坯料采购
"""
po_ids = self.env['purchase.order'].sudo().search([
('origin', 'like', self.name), ('purchase_type', '=', 'standard')])
action = {
'res_model': 'purchase.order',
'type': 'ir.actions.act_window',
}
if len(po_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': po_ids.id,
})
else:
action.update({
'name': _("采购订单列表"),
'domain': [('id', 'in', po_ids.ids)],
'view_mode': 'tree,form',
})
return action
pro_out_purchase_count = fields.Integer('坯料委外单数量', compute='_compute_pro_out_purchase_count', store=True)
@api.depends('name')
def _compute_pro_out_purchase_count(self):
for sp in self:
if sp:
po_ids = self.env['purchase.order'].sudo().search([
('origin', 'like', sp.name), ('purchase_type', '=', 'outsourcing')])
if po_ids:
sp.pro_out_purchase_count = len(po_ids)
def pro_out_purchase_order(self):
"""
坯料委外
"""
po_ids = self.env['purchase.order'].sudo().search([
('origin', 'like', self.name), ('purchase_type', '=', 'outsourcing')])
action = {
'res_model': 'purchase.order',
'type': 'ir.actions.act_window',
}
if len(po_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': po_ids.id,
})
else:
action.update({
'name': _("委外订单列表"),
'domain': [('id', 'in', po_ids.ids)],
'view_mode': 'tree,form',
})
return action
# 重写验证下发发货到bfm # 重写验证下发发货到bfm
def button_validate(self): def button_validate(self):
info = super(StockPicking, self).button_validate() info = super(StockPicking, self).button_validate()

View File

@@ -1,6 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<odoo> <odoo>
<data> <data>
<record id="add_check_out_view_picking_form" model="ir.ui.view">
</data> <field name="name">添加坯料采购单链接</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
<xpath expr="//form//sheet//div[@name='button_box']//button[@name='action_picking_move_tree']" position="before">
<button class="oe_stat_button" name="pro_purchase_order" type="object" icon="fa-credit-card"
attrs="{'invisible': [('pro_purchase_count', '=', 0)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="pro_purchase_count"/></span>
<span class="o_stat_text">坯料采购</span>
</div>
</button>
<button class="oe_stat_button" name="pro_out_purchase_order" type="object" icon="fa-credit-card"
attrs="{'invisible': [('pro_out_purchase_count', '=', 0)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="pro_out_purchase_count"/></span>
<span class="o_stat_text">坯料委外</span>
</div>
</button>
</xpath>
</field>
</record>
</data>
</odoo> </odoo>

View File

@@ -21,7 +21,7 @@ class FixtureMaterialSearch(models.Model):
image = fields.Binary('图片', related='product_id.image_1920') image = fields.Binary('图片', related='product_id.image_1920')
number = fields.Integer('总数量', compute='_compute_number') number = fields.Integer('总数量', compute='_compute_number')
usable_num = fields.Integer('可用数量', compute='_compute_number') usable_num = fields.Integer('可用数量', compute='_compute_number')
have_been_used_num = fields.Integer('用数量', compute='_compute_number') have_been_used_num = fields.Integer('用数量', compute='_compute_number')
scrap_num = fields.Integer('报废数量', compute='_compute_number') scrap_num = fields.Integer('报废数量', compute='_compute_number')
barcode_ids = fields.One2many('stock.lot', 'fixture_material_search_id', string='序列号', readonly=True) barcode_ids = fields.One2many('stock.lot', 'fixture_material_search_id', string='序列号', readonly=True)

View File

@@ -45,7 +45,8 @@ class FunctionalCuttingToolEntity(models.Model):
string='状态', store=True, default='正常') string='状态', store=True, default='正常')
current_location_id = fields.Many2one('stock.location', string='当前位置', compute='_compute_current_location_id', current_location_id = fields.Many2one('stock.location', string='当前位置', compute='_compute_current_location_id',
store=True) store=True)
current_shelf_location_id = fields.Many2one('sf.shelf.location', string='当前货位', readonly=True) current_shelf_location_id = fields.Many2one('sf.shelf.location', string='当前货位',
compute='_compute_current_location_id', store=True)
current_location = fields.Selection( current_location = fields.Selection(
[('组装后', '组装后'), ('刀具房', '刀具房'), ('线边刀库', '线边刀库'), ('机内刀库', '机内刀库')], [('组装后', '组装后'), ('刀具房', '刀具房'), ('线边刀库', '线边刀库'), ('机内刀库', '机内刀库')],
string='位置', compute='_compute_current_location_id', store=True) string='位置', compute='_compute_current_location_id', store=True)
@@ -85,6 +86,10 @@ class FunctionalCuttingToolEntity(models.Model):
if quant_id.inventory_quantity_auto_apply > 0: if quant_id.inventory_quantity_auto_apply > 0:
record.current_location_id = quant_id.location_id record.current_location_id = quant_id.location_id
if quant_id.location_id.name == '制造前': if quant_id.location_id.name == '制造前':
shelf_location_id = self.env['sf.shelf.location'].sudo().search([
('product_sn_id', '=', record.barcode_id.id)])
if shelf_location_id:
record.current_shelf_location_id = shelf_location_id.id
if not record.current_shelf_location_id: if not record.current_shelf_location_id:
record.current_location = '机内刀库' record.current_location = '机内刀库'
else: else:

View File

@@ -126,16 +126,16 @@ class StockLot(models.Model):
tool_material_search_id = fields.Many2one('sf.tool.material.search', string='刀具物料搜索') tool_material_search_id = fields.Many2one('sf.tool.material.search', string='刀具物料搜索')
fixture_material_search_id = fields.Many2one('sf.fixture.material.search', string='夹具物料搜索') fixture_material_search_id = fields.Many2one('sf.fixture.material.search', string='夹具物料搜索')
tool_material_status = fields.Selection( tool_material_status = fields.Selection(
[('未入库', '未入库'), ('可用', '可用'), ('在用', ''), ('报废', '报废')], string='状态', [('未入库', '未入库'), ('可用', '空闲'), ('在用', ''), ('报废', '报废')], string='状态',
compute='_compute_tool_material_status', store=True) compute='_compute_tool_material_status', store=True)
@api.depends('quant_ids') @api.depends('quant_ids')
def _compute_tool_material_status(self): def _compute_tool_material_status(self):
for record in self: for record in self:
if record: if record:
if record.product_id.categ_id.name in ['刀具', '夹具']: if record.product_id.categ_id.name in ['刀具']:
if record.quant_ids: if record.quant_ids:
if record.quant_ids[-1].location_id.name in ['刀具房', '夹具房']: if record.quant_ids[-1].location_id.name in ['刀具房']:
record.tool_material_status = '可用' record.tool_material_status = '可用'
elif record.quant_ids[-1].location_id.name == '刀具组装位置': elif record.quant_ids[-1].location_id.name == '刀具组装位置':
record.tool_material_status = '在用' record.tool_material_status = '在用'

Some files were not shown because too many files have changed in this diff Show More