采购协议

This commit is contained in:
guanhuan
2025-02-19 10:42:48 +08:00
parent f25d06e08f
commit 4dc19cab81
27 changed files with 597 additions and 328 deletions

View File

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

View File

@@ -24,9 +24,6 @@
# always loaded
'data': [
'security/ir.model.access.csv',
'data/documents_data.xml',
'wizards/upload_file_wizard_view.xml',
'views/views.xml',
],
# only loaded in demonstration mode

View File

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

View File

@@ -1,21 +0,0 @@
# -*- coding: utf-8 -*-
# from odoo import http
# class JikimoPurchaseTierValidation(http.Controller):
# @http.route('/jikimo_purchase_tier_validation/jikimo_purchase_tier_validation', auth='public')
# def index(self, **kw):
# return "Hello, world"
# @http.route('/jikimo_purchase_tier_validation/jikimo_purchase_tier_validation/objects', auth='public')
# def list(self, **kw):
# return http.request.render('jikimo_purchase_tier_validation.listing', {
# 'root': '/jikimo_purchase_tier_validation/jikimo_purchase_tier_validation',
# 'objects': http.request.env['jikimo_purchase_tier_validation.jikimo_purchase_tier_validation'].search([]),
# })
# @http.route('/jikimo_purchase_tier_validation/jikimo_purchase_tier_validation/objects/<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

@@ -1,11 +0,0 @@
<?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

@@ -1,30 +0,0 @@
<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

@@ -21,12 +21,8 @@ class jikimo_purchase_tier_validation(models.Model):
def button_confirm(self):
for record in self:
# if record.need_validation and record.validation_status != 'validated':
# raise ValidationError(_('此操作需要至少对一条记录进行审批。\n请发起审批申请。'))
if record.state in ['to approve']:
raise ValidationError(_('请先完成审批。'))
# if record.state == 'approved':
# record.state = 'purchase'
res = super(jikimo_purchase_tier_validation, self).button_confirm()
for record in self:
if record.state == 'approved':
@@ -39,45 +35,8 @@ class jikimo_purchase_tier_validation(models.Model):
record.message_subscribe([record.partner_id.id])
return res
# def button_confirm(self):
# self = self.with_context(skip_validation=True)
# return super().button_confirm()
#
# def _check_state_conditions(self, vals):
# self.ensure_one()
# if self._context.get('skip_validation'):
# return False
# return (
# self._check_state_from_condition()
# and vals.get(self._state_field) in self._state_to
# )
def request_validation(self):
for record in self:
error_messages = []
# 检查必填字段
required_fields = {
'partner_ref': '合同名称',
'contract_number': '合同编号'
}
missing_fields = [
name for field, name in required_fields.items()
if not record[field]
]
if missing_fields:
error_messages.append('* 如下字段要求必须填写:%s' % ''.join(missing_fields))
# 检查合同文件
if not record.contract_document_id:
error_messages.append('* 必须点击上传合同文件')
# 如果有任何错误,一次性显示所有错误信息
if error_messages:
raise ValidationError('\n'.join(error_messages))
# 添加通知消息
if hasattr(record, 'message_post'):
current_user = self.env.user.name

View File

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

@@ -1,24 +0,0 @@
<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

@@ -23,76 +23,10 @@
<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

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

View File

@@ -1,114 +0,0 @@
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

@@ -10,7 +10,7 @@
""",
'category': 'sf',
'website': 'https://www.sf.cs.jikimo.com',
'depends': ['sf_base', 'base_setup','sf_bf_connect'],
'depends': ['sf_base', 'base_setup','sf_bf_connect','sf_sale'],
'data': [
'data/ir_cron_data.xml',
'security/ir.model.access.csv',

View File

@@ -10,18 +10,23 @@
""",
'category': 'sf',
'website': 'https://www.sf.jikimo.com',
'depends': ['sale', 'sale_management', 'web_widget_model_viewer', 'sf_base', 'account', 'purchase', 'delivery'],
'depends': ['sale', 'sale_management', 'web_widget_model_viewer', 'sf_base', 'account', 'purchase', 'delivery',
'purchase_requisition', 'purchase_requisition_stock', 'documents'],
'data': [
'data/documents_data.xml',
'security/group_security.xml',
'security/ir.model.access.csv',
'wizard/sale_order_wizard_views.xml',
'wizard/purchase_order_wizard_views.xml',
'wizard/upload_file_upload_view.xml',
'wizard/purchase_requisition_wizard_views.xml',
'data/cron_data.xml',
'views/sale_team.xml',
'views/sale_order_view.xml',
'views/res_partner_view.xml',
'views/purchase_order_view.xml',
'views/quick_easy_order_view.xml',
'views/purchase_requisition_view.xml',
'views/purchase_menu.xml'
],
'assets': {

View File

@@ -0,0 +1,23 @@
<?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>
<record id="documents_purchase_contracts_folder_1" model="documents.folder">
<field name="name">废弃</field>
<field name="parent_folder_id" ref="documents_purchase_contracts_folder"/>
</record>
<record id="documents_purchase_contracts_folder_2" model="documents.folder">
<field name="name">通过</field>
<field name="parent_folder_id" ref="documents_purchase_contracts_folder"/>
</record>
<record id="documents_purchase_contracts_folder_3" model="documents.folder">
<field name="name">待审</field>
<field name="parent_folder_id" ref="documents_purchase_contracts_folder"/>
</record>
</data>
</odoo>

10
sf_sale/i18n/zh_CN.po Normal file
View File

@@ -0,0 +1,10 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * purchase_requisition
#
#. module: purchase_requisition
#: model:ir.actions.act_window,name:purchase_requisition.action_purchase_requisition
#: model:ir.ui.menu,name:purchase_requisition.menu_purchase_requisition_pro_mgt
msgid "Blanket Orders"
msgstr "采购协议"

View File

@@ -4,4 +4,4 @@ from . import quick_easy_order_old
from . import auto_quatotion_common
from . import parser_and_calculate_work_time
from . import preload_datas_functions
from . import purchase_requisition

View File

@@ -0,0 +1,210 @@
from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
import logging
_logger = logging.getLogger(__name__)
class PurchaseRequisition(models.Model):
_inherit = 'purchase.requisition'
partner_ref = fields.Char(string='合同名称', required=True)
contract_number = fields.Char(string='合同编码', size=20, required=True)
payment_term_id = fields.Many2one('account.payment.term', '付款条件',
domain="['|', ('company_id', '=', False), ('company_id', '=', company_id)]",
required=True)
contract_summary = fields.Text(string='合同概况', required=True)
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 upload_contract_file(self):
self.ensure_one()
action = {
'type': 'ir.actions.act_window',
'name': _('上传合同文件'),
'res_model': 'ir.attachment.upload', # 我们需要创建一个新的向导模型
'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.requisition',
'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,
}
}
def action_in_progress(self):
self.ensure_one()
# 检查合同文件
if not self.contract_document_id:
raise ValidationError('* 必须点击上传合同文件')
if self.origin:
purchase_order = self.env['purchase.order'].search([('name', '=', self.origin)], limit=1)
if purchase_order:
if not purchase_order.requisition_id:
# 绑定采购协议
self.purchase_ids += purchase_order
else:
raise ValidationError('源单据已绑定采购协议,请手动清除源单据解绑。')
res = super(PurchaseRequisition, self).action_in_progress()
# 将合同文档移动到废弃
for requisition in self:
if requisition.contract_document_id:
workspace = self.update_documents_folder('通过')
requisition.contract_document_id.write({
'folder_id': workspace.id,
})
return res
def action_cancel(self):
for requisition in self:
if requisition.purchase_ids:
raise ValidationError(_('已关联采购订单,不能取消该采购协议'))
res = super(PurchaseRequisition, self).action_cancel()
# 将合同文档移动到废弃
for requisition in self:
if requisition.contract_document_id:
workspace = self.update_documents_folder('废弃')
requisition.contract_document_id.write({
'folder_id': workspace.id,
})
return res
def action_draft(self):
res = super(PurchaseRequisition, self).action_draft()
for requisition in self:
if requisition.contract_document_id:
workspace = self.update_documents_folder('待审')
requisition.contract_document_id.write({
'folder_id': workspace.id,
})
return res
def update_documents_folder(self, documents_folder_name):
workspace_parent = self.env['documents.folder'].search([('name', '=', '采购合同')], limit=1)
workspace = self.env['documents.folder'].search(
[('name', '=', documents_folder_name), ('parent_folder_id', '=', workspace_parent.id)], limit=1)
return workspace
@api.constrains('line_ids')
def check_line_ids(self):
if self.origin:
purchase_order = self.env['purchase.order'].search([('name', '=', self.origin)], limit=1)
if purchase_order:
error_products = []
# 获取采购订单中的所有产品
purchase_order_products = purchase_order.order_line.mapped('product_id')
if self.line_ids:
for line in self.line_ids:
# 检查当前行的产品在采购订单中的数量
order_line = purchase_order.order_line.filtered(lambda ol: ol.product_id == line.product_id)
if order_line and line.product_qty < order_line.product_qty:
error_products.append(line.product_id.name) # 收集不符合条件的产品名称
else:
raise ValidationError(
_('采购协议的产品和数量应达超源单据的产品和数量,请修改产品数量或修改源单据。'))
# 检查采购订单中的产品是否都在采购协议中
for product in purchase_order_products:
if product not in self.line_ids.mapped('product_id'):
raise ValidationError(
_('采购协议的产品和数量应达超源单据的产品和数量,请修改产品数量或修改源单据。'))
if error_products:
raise ValidationError(
_('产品 %s 的数量不能小于源单据数量,当前无法保存。') % (', '.join(error_products)))
class PurchaseRequisitionLine(models.Model):
_inherit = 'purchase.requisition.line'
materials_id = fields.Many2one('sf.production.materials', string='材料', compute='_compute_product_info')
materials_type_id = fields.Many2one('sf.materials.model', string='规格型号', compute='_compute_product_info')
part_number = fields.Char(string='零件图号', compute='_compute_product_info')
delivery_date = fields.Date(string='交货时间')
@api.depends('product_id')
def _compute_product_info(self):
for line in self:
if line.product_id:
line.materials_id = line.product_id.materials_id
line.materials_type_id = line.product_id.materials_type_id
line.part_number = line.product_id.part_number
else:
line.materials_id = False
line.materials_type_id = False
line.part_number = False
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if 'product_qty' in vals and vals.get('product_qty') <= 0:
raise UserError('请对【产品】中的【数量】进行输入')
if 'price_unit' in vals and vals.get('price_unit') <= 0:
raise UserError('请对【产品】中的【单价】进行输入')
return super(PurchaseRequisitionLine, self).create(vals_list)
def write(self, vals):
if 'product_qty' in vals and vals.get('product_qty') <= 0:
raise UserError('请对【产品】中的【数量】进行输入')
if 'price_unit' in vals and vals.get('price_unit') <= 0:
raise UserError('请对【产品】中的【单价】进行输入')
return super(PurchaseRequisitionLine, self).write(vals)

View File

@@ -132,7 +132,7 @@ class ReSaleOrder(models.Model):
'name': '%s/%s/%s/%s/%s/%s' % (
self.format_float(product.model_long),
self.format_float(product.model_width),
self.format_float(product.model_height),
self.format_float(product.model_height),
self.format_float(product.model_volume),
machining_accuracy_name,
product.materials_id.name),
@@ -303,13 +303,39 @@ class RePurchaseOrder(models.Model):
string='采购类型', default='standard', store=True, compute='_compute_purchase_type')
# 合同编号
contract_number = fields.Char(string='合同编号', size=20)
contract_number = fields.Char(related='requisition_id.contract_number', string='合同编号', size=20)
# 合同概况
contract_summary = fields.Text(string='合同概况')
# 选择是否为紧急采购
urgent_purchase = fields.Selection([('no', ''), ('yes', '')], string='紧急采购', default='no')
purchase_requisition_count = fields.Integer('采购协议数量', compute='_compute_purchase_requisition_count')
partner_ref = fields.Char(related='requisition_id.partner_ref')
payment_term_id = fields.Many2one(related='requisition_id.payment_term_id')
requisition_id = fields.Many2one('purchase.requisition', string='采购协议', copy=False, readonly=True)
show_create_requisition_button = fields.Boolean(string='显示创建采购协议按钮',
compute='_compute_show_create_requisition_button'
)
show_requisition_fields = fields.Boolean(string='显示协议按钮', compute='_compute_show_requisition_fields')
@api.depends('requisition_id')
def _compute_show_requisition_fields(self):
for order in self:
order.show_requisition_fields = bool(order.requisition_id and order.requisition_id.state == 'ongoing')
@api.depends('requisition_id')
def _compute_show_create_requisition_button(self):
for order in self:
purchase_requisition = self.env['purchase.requisition'].search_count([('origin', '=', order.name)])
order.show_create_requisition_button = purchase_requisition > 0
def _compute_purchase_requisition_count(self):
for record in self:
record.purchase_requisition_count = len(record.requisition_id)
@api.depends('origin')
def _compute_purchase_type(self):
for purchase in self:
@@ -344,6 +370,26 @@ class RePurchaseOrder(models.Model):
for item in self:
if not item.order_line:
raise UserError('该询价单未添加【产品】,请进行添加')
missing_products = []
over_ordered_products = []
if item.requisition_id:
requisition_product_ids = item.requisition_id.line_ids.mapped('product_id.id')
for line in item.order_line:
if line.product_id.id not in requisition_product_ids:
missing_products.append(line.product_id.name)
else:
requisition_line = item.requisition_id.line_ids.filtered(
lambda r: r.product_id.id == line.product_id.id)
remaining_qty = requisition_line.product_qty - requisition_line.qty_ordered
if requisition_line and line.product_qty > remaining_qty:
over_ordered_products.append(
f'{line.product_id.name}{remaining_qty}{requisition_line.product_uom_id.name}')
if missing_products:
raise UserError('关联协议不存在产品 %s ,无法保存。' % ''.join(missing_products))
if over_ordered_products:
raise UserError('当前订购数量超过了关联协议的剩余数量,无法保存。\n涉及产品及剩余数量有:\n%s' % '\n'.join(
over_ordered_products))
for line in item.order_line:
if not line.product_id:
raise UserError('【产品】未添加,请进行添加')
@@ -399,6 +445,21 @@ class RePurchaseOrder(models.Model):
# raise ValidationError('【%s】已存在请勿重复添加' % product[-1].name)
def button_confirm(self):
over_ordered_products = []
for item in self:
if item.requisition_id:
requisition_product_ids = item.requisition_id.line_ids.mapped('product_id.id')
for line in item.order_line:
if line.product_id.id in requisition_product_ids:
requisition_line = item.requisition_id.line_ids.filtered(
lambda r: r.product_id.id == line.product_id.id)
remaining_qty = requisition_line.product_qty - requisition_line.qty_ordered
if requisition_line and line.product_qty > remaining_qty:
over_ordered_products.append(
f'{line.product_id.name}{remaining_qty} {requisition_line.product_uom_id.name}')
if over_ordered_products:
raise UserError('当前订购数量超过了关联协议的剩余数量,无法保存。\n涉及产品及剩余数量有:\n%s' % '\n'.join(
over_ordered_products))
result = super(RePurchaseOrder, self).button_confirm()
for item in self:
# 确认订单时,自动分配序列号
@@ -439,6 +500,90 @@ class RePurchaseOrder(models.Model):
purchase_order_overdue.write({'delivery_warning': 'normal'})
return last_overdue_order, last_warning_order
def button_create_requisition(self):
self.ensure_one()
line_ids = []
for order_line in self.order_line:
line_values = {
'product_id': order_line.product_id.id,
'product_uom_id': order_line.product_uom.id,
'product_qty': order_line.product_qty,
'price_unit': order_line.price_unit,
}
line_ids.append((0, 0, line_values))
initial_data = {
'vendor_id': self.partner_id.id,
'origin': self.name,
'line_ids': line_ids,
}
return {
'type': 'ir.actions.act_window',
'res_model': 'purchase.requisition',
'view_mode': 'form',
'target': 'current',
'context': {
'default_origin': initial_data['origin'],
'default_line_ids': initial_data['line_ids'],
'default_vendor_id': initial_data['vendor_id'],
}
}
def button_link_requisition(self):
requisition_ids = self.env['purchase.requisition'].sudo().search(
[('state', '=', 'ongoing'), ('vendor_id', '=', self.partner_id.id),
('line_ids.product_id', 'in', self.order_line.mapped('product_id.id'))])
# 新增的逻辑过滤出符合条件的requisition_ids
valid_requisition_ids = []
for requisition in requisition_ids:
for line in requisition.line_ids:
if line.product_id in self.order_line.mapped(
'product_id') and line.product_qty - line.qty_ordered >= self.order_line.filtered(
lambda ol: ol.product_id == line.product_id).product_qty:
valid_requisition_ids.append(requisition.id)
break
action = {
'res_model': 'purchase.requisition',
'type': 'ir.actions.act_window',
'name': _("选择采购申请"),
'domain': [('id', 'in', valid_requisition_ids)],
'views': [[self.env.ref('sf_sale.purchase_requisition_wizard_tree_view').id, 'list']],
'target': 'new',
'context': {'purchase_order_id': self.id},
}
return action
def button_unlink_requisition(self):
self.ensure_one()
for line in self.order_line:
if self.requisition_id:
requisition_line = self.requisition_id.line_ids.filtered(lambda r: r.product_id.id == line.product_id.id)
if requisition_line:
self.requisition_id.origin = False
self.requisition_id.purchase_ids = [(3, self.id)]
def action_view_requisition(self):
self.ensure_one()
requisition_ids = self.requisition_id.ids
action = {
'res_model': 'purchase.requisition',
'type': 'ir.actions.act_window',
}
if len(requisition_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': requisition_ids[0],
})
else:
action.update({
'name': _("采购协议来源%s", self.name),
'domain': [('id', 'in', requisition_ids)],
'view_mode': 'tree,form',
})
return action
class PurchaseOrderLine(models.Model):
_inherit = 'purchase.order.line'

View File

@@ -119,5 +119,5 @@ access_stock_scrap_group_sale_director,stock_scrap_group_sale_director,stock.mod
access_account_move_group_plan_dispatch,account_move_group_plan_dispatch,account.model_account_move,sf_base.group_plan_dispatch,1,1,1,0
access_ir_attachment_upload,ir.attachment.upload,model_ir_attachment_upload,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
119
120
121
122
123

View File

@@ -25,6 +25,14 @@
data-hotkey="y" groups="stock.group_stock_user"/>
<button name="button_cancel" states="draft,to approve,sent,purchase" string="取消" type="object"
data-hotkey="x"/>
<field name="show_create_requisition_button" invisible="1"/>
<field name="show_requisition_fields" invisible="1"/>
<button name="button_create_requisition" string="创建协议" type="object"
attrs="{'invisible': [('show_create_requisition_button', '!=', False)]}"/>
<button name="button_link_requisition" string="关联协议" type="object"
attrs="{'invisible': [('requisition_id', '!=', False),('show_requisition_fields', '=', False)]}"/>
<button name="button_unlink_requisition" string="解绑协议" type="object"
attrs="{'invisible': [('show_requisition_fields', '=', False)]}"/>
</xpath>
<xpath expr="//header/button[@name='button_cancel'][2]" position="attributes">
<attribute name="invisible">1</attribute>
@@ -139,7 +147,7 @@
</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"/>
<!-- <field name="contract_summary"/>-->
</xpath>
<field name="partner_ref" position="attributes">
<attribute name="attrs">{'readonly': [('state', 'in', ['purchase'])]}
@@ -210,6 +218,17 @@
<xpath expr="//sheet/group/group[2]/div[@name='date_planned_div']" position="attributes">
<attribute name="invisible">1</attribute>
</xpath>
<xpath expr="//div[@name='button_box']" position="inside">
<button class="oe_stat_button" name="action_view_requisition" type="object" icon="fa-file"
attrs="{'invisible': [('purchase_requisition_count', '=', 0)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="purchase_requisition_count"/>
</span>
<span class="o_stat_text">采购协议</span>
</div>
</button>
</xpath>
</field>
</record>

View File

@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data>
<record model="ir.ui.view" id="view_purchase_requisition_form_inherit_sf">
<field name="name">purchase.requisition.form.inherit.sf</field>
<field name="model">purchase.requisition</field>
<field name="inherit_id" ref="purchase_requisition.view_purchase_requisition_form"/>
<field name="arch" type="xml">
<xpath expr="//header/button[@name='action_in_progress'][1]" position="after">
<field name="is_upload_contract_file" invisible="1"/>
<button name="upload_contract_file" string="上传合同" type="object" class="oe_highlight"
attrs="{'invisible': ['|', ('is_upload_contract_file', '=', True), ('state', 'not in', ['draft'])]}"/>
]}"/>
<button name="delete_contract_file" string="删除合同" type="object" class="oe_highlight"
attrs="{'invisible': ['|', ('is_upload_contract_file', '=', False), ('state', 'not in', ['draft'])]}"/>
</xpath>
<xpath expr="//group" position="replace">
<group>
<group>
<field name="type_id" attrs="{'readonly': [('state','!=','draft')]}"/>
<field name="date_end"
attrs="{'readonly': [('state','not in',('draft','in_progress','open','ongoing'))]}"/>
<field name="ordering_date"
attrs="{'readonly': [('state','not in',('draft','in_progress','open','ongoing'))]}"/>
<field name="schedule_date"
attrs="{'readonly': [('state','not in',('draft','in_progress','open','ongoing'))]}"/>
<field name="origin" placeholder="e.g. PO0025"
attrs="{'readonly': [('state', '!=', 'draft')]}"/>
<field name="picking_type_id" options="{'no_open': True, 'no_create': True}"
groups="stock.group_adv_location" attrs="{'readonly': [('state', '!=', 'draft')]}"/>
<field name="company_id" groups="base.group_multi_company" options="{'no_create': True}"
attrs="{'readonly': [('state','not in',('draft'))]}"/>
</group>
<group>
<field name="is_quantity_copy" invisible='1'/>
<field name="user_id"
attrs="{'readonly': [('state','not in',('draft','in_progress','open'))]}"
domain="[('share', '=', False)]"/>
<field name="vendor_id" context="{'res_partner_search_mode': 'supplier'}"
attrs="{'required': [('is_quantity_copy', '=', 'none')], 'readonly': [('state', 'in', ['ongoing','done'])]}"/>
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="partner_ref"/>
<field name="contract_number"/>
<field name="payment_term_id" options="{'no_create': True}"/>
<field name="contract_summary"/>
</group>
</group>
</xpath>
<xpath expr="//page[@name='products']//tree//field[@name='product_id']" position="after">
<field name="materials_id"/>
<field name="materials_type_id"/>
<field name="part_number"/>
</xpath>
<xpath expr="//page[@name='products']//tree//field[@name='price_unit']" position="after">
<field name="delivery_date"/>
</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>
</data>
</odoo>

View File

@@ -1,2 +1,4 @@
from . import sale_order_wizard
from . import purchase_order_wizard
from . import upload_file_upload
from . import purchase_requisition_wizard

View File

@@ -0,0 +1,20 @@
from odoo import models, fields, api, _
import logging
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
class PurchaseRequisition(models.Model):
_inherit = 'purchase.requisition'
def action_confirm(self):
purchase_order_id = self.env.context.get('purchase_order_id')
purchase_order = self.env['purchase.order'].sudo().search([('id', '=', purchase_order_id)])
if purchase_order.requisition_id.id == self.id:
raise UserError(_('该采购订单已绑定%s协议') % purchase_order.name)
if purchase_order.requisition_id:
# 先解绑
purchase_order.button_unlink_requisition()
purchase_order.write({'requisition_id': self.id})
self.purchase_ids += purchase_order

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.ui.view" id="purchase_requisition_wizard_tree_view">
<field name="name">purchase.requisition.wizard.tree.view</field>
<field name="model">purchase.requisition</field>
<field name="arch" type="xml">
<tree create="false" delete="false">
<field name="name"/>
<field name="partner_ref"/>
<field name="contract_number"/>
<field name="create_date"/>
<button string="关联" type="object" name="action_confirm" class="btn-primary"/>
</tree>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,60 @@
from odoo import models, fields, api, _
class IrAttachmentUpload(models.TransientModel):
_name = 'ir.attachment.upload'
_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()
# 获取当前用户的 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['purchase.requisition'].update_documents_folder('待审')
# 创建 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_requisition = self.env['purchase.requisition'].browse(self.res_id)
purchase_requisition.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

View File

@@ -1,8 +1,8 @@
<?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>
<record id="view_upload_file_upload_form" model="ir.ui.view">
<field name="name">ir.attachment.upload.form</field>
<field name="model">ir.attachment.upload</field>
<field name="arch" type="xml">
<form string="上传文件">
<group>