Compare commits

...

40 Commits

Author SHA1 Message Date
yuxianghui
7b4499184d 添加禁止负库存功能 2025-07-09 16:58:20 +08:00
管欢
3be1c4c529 Accept Merge Request #2272: (feature/物料需求计划管理 -> develop)
Merge Request: 更新报错

Created By: @管欢
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @管欢
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2272
2025-07-09 10:26:45 +08:00
guanhuan
60e7415a27 更新报错 2025-07-09 10:25:19 +08:00
管欢
22763903a4 Accept Merge Request #2271: (feature/物料需求计划管理 -> develop)
Merge Request: 更新报错

Created By: @管欢
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @管欢
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2271
2025-07-09 10:19:50 +08:00
guanhuan
4c6cf0ba5e 更新报错 2025-07-09 09:54:58 +08:00
管欢
02012b2c52 Accept Merge Request #2270: (feature/物料需求计划管理 -> develop)
Merge Request: 物料需求计划

Created By: @管欢
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @管欢
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2270
2025-07-09 09:43:44 +08:00
guanhuan
a2b2efed7d 交货数量修改 2025-07-08 15:46:32 +08:00
guanhuan
925c59e1fe 采购申请添加需求明细字段 2025-07-08 11:25:00 +08:00
胡尧
f86e27b45e Accept Merge Request #2269: (feature/新增plm模块 -> develop)
Merge Request: 删除plm模块代码

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2269?initial=true
2025-07-07 15:00:13 +08:00
胡尧
b704f99a29 删除plm模块代码 2025-07-07 14:59:48 +08:00
胡尧
1ac6314f5f Accept Merge Request #2268: (feature/新增plm模块 -> develop)
Merge Request: 新增

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2268?initial=true
2025-07-07 12:16:56 +08:00
胡尧
8b3eef1256 新增 2025-07-07 11:59:14 +08:00
guanhuan
22ccb095ee 需求计划下达计划 2025-07-07 10:46:27 +08:00
guanhuan
9db4ea745b Merge branch 'refs/heads/develop' into feature/物料需求计划管理 2025-07-04 17:03:23 +08:00
guanhuan
60fd4d4a73 需求计划下达计划 2025-07-04 17:01:56 +08:00
禹翔辉
c6c4331c0b Accept Merge Request #2267: (feature/调拨单质检 -> develop)
Merge Request: 处理  成品出库单创建欠单后生成的调拨单详情页中质量检查智能按钮默认展示为通过状态  问题

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2267
2025-07-04 13:49:35 +08:00
yuxianghui
10c6f59f52 处理 成品出库单创建欠单后生成的调拨单详情页中质量检查智能按钮默认展示为通过状态 问题 2025-07-04 13:48:16 +08:00
guanhuan
df4995fa24 需求计划下达计划 2025-07-04 10:18:28 +08:00
guanhuan
6e877d0449 需求计划下达计划 2025-07-03 17:32:11 +08:00
guanhuan
3822d78f35 需求计划供货方式修改 2025-07-03 14:33:19 +08:00
胡尧
b23e50daa6 Accept Merge Request #2266: (feature/6711 -> develop)
Merge Request: 解决采购申请创建的采购单取消后导致后续单据无法就绪的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2266?initial=true
2025-07-02 17:32:14 +08:00
胡尧
4615f1576f 解决采购申请创建的采购单取消后导致后续单据无法就绪的问题 2025-07-02 17:31:43 +08:00
guanhuan
0350f78710 Merge branch 'refs/heads/develop' into feature/物料需求计划管理
# Conflicts:
#	sf_demand_plan/models/sf_production_demand_plan.py
#	sf_demand_plan/views/demand_plan.xml
2025-07-02 08:57:20 +08:00
胡尧
c8f1676de9 解决采购申请创建的采购单取消后导致调拨单不能就绪的问题 2025-07-01 17:55:07 +08:00
guanhuan
b267f2351d 需求计划开发 2025-07-01 14:34:27 +08:00
guanhuan
c31cc868a9 物料需求计划管理 2025-06-30 17:10:25 +08:00
guanhuan
334d406c35 需求计划 2025-06-30 11:29:40 +08:00
guanhuan
048f07aba4 新增坯料类型 2025-06-27 17:44:32 +08:00
guanhuan
c03cda8250 物料需求计划管理 2025-06-27 15:23:05 +08:00
guanhuan
50a066c23a 物料需求计划管理 2025-06-26 17:52:40 +08:00
guanhuan
fe4659987f 物料需求计划管理 2025-06-26 17:18:03 +08:00
guanhuan
c14e2c19bf 物料需求计划管理 2025-06-26 11:13:56 +08:00
guanhuan
413cf43ea0 物料需求计划管理 2025-06-25 15:33:24 +08:00
guanhuan
deda36a0f9 物料需求计划管理 2025-06-25 15:01:02 +08:00
guanhuan
8a953a1417 Merge branch 'refs/heads/feature/库存路线' into feature/物料需求计划管理
# Conflicts:
#	sf_demand_plan/__manifest__.py
#	sf_demand_plan/models/sf_production_demand_plan.py
#	sf_demand_plan/security/ir.model.access.csv
2025-06-25 14:32:18 +08:00
guanhuan
d046fd5298 移除销售订单供货方式确认 2025-06-25 14:22:27 +08:00
guanhuan
9f00fddc09 物料需求计划管理 2025-06-25 09:57:19 +08:00
guanhuan
f41d3558d2 物料需求计划管理 2025-06-24 17:46:55 +08:00
guanhuan
02b4f76326 物料需求计划 2025-06-24 11:17:41 +08:00
guanhuan
2f26aee90a 物料需求计划 2025-06-23 17:21:50 +08:00
31 changed files with 1549 additions and 436 deletions

View File

@@ -94,12 +94,20 @@ class PurchaseOrder(models.Model):
def button_cancel(self): def button_cancel(self):
""" """
将取消的采购订单关联的库存移动撤销 1. 先将采购订单行与目标库存移动断开链接避免采购单取消后调拨单被调整为mts的问题
2. 取消采购订单
3. 将采购订单行与目标库存移动重新建立链接
""" """
move_ids = self.order_line.move_dest_ids.filtered(lambda move: move.state != 'done' and not move.scrapped) created_purchase_request_line_ids = {}
if self.order_line.move_dest_ids.created_purchase_request_line_id:
move_ids = self.order_line.move_dest_ids.filtered(lambda move: move.state != 'done' and not move.scrapped)
created_purchase_request_line_ids = {move.id: move.created_purchase_request_line_id for move in move_ids}
self.order_line.write({'move_dest_ids': [(5, 0, 0)]})
res =super(PurchaseOrder, self).button_cancel() res =super(PurchaseOrder, self).button_cancel()
if move_ids.mapped('created_purchase_request_line_id'): for move_id, created_purchase_request_line_id in created_purchase_request_line_ids.items():
move_ids.write({'state': 'waiting', 'is_done': False}) self.env['stock.move'].browse(move_id).created_purchase_request_line_id = created_purchase_request_line_id
# if move_ids.mapped('created_purchase_request_line_id'):
# move_ids.write({'state': 'waiting', 'is_done': False})
return res return res
def write(self, vals): def write(self, vals):

View File

@@ -44,7 +44,7 @@ class StatusChange(models.Model):
else: else:
action.update({ action.update({
'name': _("%s生成采购请求单", self.name), 'name': _("%s生成采购请求单", self.name),
'domain': [('id', 'in', pr_ids)], 'domain': [('id', 'in', pr_ids.ids)],
'view_mode': 'tree,form', 'view_mode': 'tree,form',
}) })
return action return action

View File

@@ -2,7 +2,7 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details. # Part of Odoo. See LICENSE file for full copyright and licensing details.
{ {
'name': '机企猫智能工厂 需求计划', 'name': '机企猫智能工厂 需求计划',
'version': '1.0', 'version': '1.1',
'summary': '智能工厂计划管理', 'summary': '智能工厂计划管理',
'sequence': 1, 'sequence': 1,
'description': """ 'description': """
@@ -14,9 +14,13 @@
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'data/stock_route_group.xml', 'data/stock_route_group.xml',
'views/demand_plan_info.xml',
'views/demand_plan.xml', 'views/demand_plan.xml',
'views/stock_route.xml', 'views/stock_route.xml',
'views/sale_order_views.xml',
'wizard/sf_demand_plan_print_wizard_view.xml', 'wizard/sf_demand_plan_print_wizard_view.xml',
'wizard/sf_release_plan_wizard_views.xml',
'views/menu_view.xml',
], ],
'demo': [ 'demo': [
], ],

View File

@@ -0,0 +1,25 @@
# migrations/1.1.0/post-migrate.py
import os
import csv
import logging
from odoo import api, SUPERUSER_ID
_logger = logging.getLogger(__name__)
def migrate(cr, version):
# 获取环境
env = api.Environment(cr, SUPERUSER_ID, {})
ProductionLine = env['sf.production.demand.plan']
DemandPlan = env['sf.demand.plan']
lines = ProductionLine.search([('demand_plan_id', '=', False)])
for line in lines:
vals = {
'sale_order_id': line.sale_order_id.id,
'sale_order_line_id': line.sale_order_line_id.id,
'line_ids': line.ids
}
new_plan = DemandPlan.create(vals)
line.write({'demand_plan_id': new_plan.id})

View File

@@ -1,5 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import sf_demand_plan
from . import sf_production_demand_plan from . import sf_production_demand_plan
from . import sale_order from . import sale_order
from . import stock_route from . import stock_route
from . import mrp_bom
from . import mrp_production
from . import stock_rule
from . import purchase_request
from . import purchase_order

View File

@@ -0,0 +1,20 @@
from odoo import models, fields
class MrpBom(models.Model):
_inherit = 'mrp.bom'
# 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品后再次进行创建bom
def bom_create(self, product, bom_type, product_type, code=None):
bom_id = self.env['mrp.bom'].create({
'product_tmpl_id': product.product_tmpl_id.id,
'type': bom_type,
# 'subcontractor_id': '' or subcontract.partner_id.id,
'product_qty': 1,
'product_uom_id': 1,
'code': code
})
if bom_type == 'subcontract' and product_type is not False:
subcontract = self.get_supplier(product.materials_type_id)
bom_id.subcontractor_id = subcontract.partner_id.id
return bom_id

View File

@@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, api
class MrpProduction(models.Model):
_inherit = 'mrp.production'
demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan",
string="需求计划明细", readonly=True)
@api.depends('demand_plan_line_id')
def _compute_production_type(self):
for production in self:
if production.demand_plan_line_id.supply_method == 'automation':
production.production_type = '自动化产线加工'
elif production.demand_plan_line_id.supply_method == 'manual':
production.production_type = '人工线下加工'
else:
production.production_type = None
def _get_purchase_request(self):
"""获取跟制造订单相关的采购申请单(根据采购申请单行项目的产品匹配)"""
pr_ids = self.env['purchase.request'].sudo().search(
[('line_ids.demand_plan_line_id', 'in', self.demand_plan_line_id.ids)])
return pr_ids
@api.depends('procurement_group_id', 'procurement_group_id.stock_move_ids.group_id')
def _compute_picking_ids(self):
for order in self:
if order.product_id.product_tmpl_id.single_manufacturing == True and not order.is_remanufacture:
first_order = self.env['mrp.production'].search(
[('demand_plan_line_id', '=', order.demand_plan_line_id.id), ('product_id', '=', order.product_id.id)], limit=1, order='id asc')
order.picking_ids = self.env['stock.picking'].search([
('group_id', '=', first_order.procurement_group_id.id), ('group_id', '!=', False),
])
order.delivery_count = len(first_order.picking_ids)
else:
order.picking_ids = self.env['stock.picking'].search([
('group_id', '=', order.procurement_group_id.id), ('group_id', '!=', False),
])
order.delivery_count = len(order.picking_ids)

View File

@@ -0,0 +1,44 @@
from odoo import api, fields, models, _
from odoo.tools import float_compare
class PurchaseOrder(models.Model):
_inherit = 'purchase.order'
demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan",
string="需求计划明细", readonly=True)
def button_confirm(self):
if self.demand_plan_line_id:
self = self.with_context(
demand_plan_line_id=self.demand_plan_line_id.id
)
res = super(PurchaseOrder, self).button_confirm()
return res
@api.depends('origin', 'demand_plan_line_id')
def _compute_purchase_type(self):
for purchase in self:
if purchase.order_line[0].product_id.categ_id.name == '坯料':
if purchase.order_line[0].product_id.materials_type_id.gain_way == '外协':
purchase.purchase_type = 'outsourcing'
else:
if purchase.demand_plan_line_id.supply_method == 'outsourcing':
purchase.purchase_type = 'outsourcing'
elif purchase.demand_plan_line_id.supply_method == 'purchase':
purchase.purchase_type = 'outside'
@api.model
def create(self, vals):
res = super(PurchaseOrder, self).create(vals)
if not res.demand_plan_line_id:
origin = [origin.replace(' ', '') for origin in res.origin.split(',')]
if self.env.context.get('demand_plan_line_id'):
res.demand_plan_line_id = self.env.context.get('demand_plan_line_id')
elif 'MO' in res.origin:
# 原单据是制造订单
mp_ids = self.env['mrp.production'].sudo().search([('name', 'in', origin)])
if mp_ids:
res.demand_plan_line_id = mp_ids[0].demand_plan_line_id.id
return res

View File

@@ -0,0 +1,110 @@
from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
class PurchaseRequestLine(models.Model):
_inherit = 'purchase.request.line'
_description = '采购申请明细'
supply_method = fields.Selection([
('automation', "自动化产线加工"),
('manual', "人工线下加工"),
('purchase', "外购"),
('outsourcing', "委外加工"),
], string='供货方式', readonly=True)
demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan",
string="需求计划明细", readonly=True)
@api.depends('demand_plan_line_id')
def _compute_supply_method(self):
for prl in self:
if prl.demand_plan_line_id:
prl.supply_method = prl.demand_plan_line_id.supply_method
else:
prl.supply_method = None
class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
_inherit = "purchase.request.line.make.purchase.order"
def make_purchase_order(self):
res = []
purchase_obj = self.env["purchase.order"]
po_line_obj = self.env["purchase.order.line"]
purchase = False
if len(set([item_id.line_id.supply_method for item_id in self.item_ids])) > 1:
raise ValidationError('不同供货方式不可合并创建询价单!')
for item in self.item_ids:
line = item.line_id
if item.product_qty <= 0.0:
raise UserError(_("Enter a positive quantity."))
if self.purchase_order_id:
purchase = self.purchase_order_id
if not purchase:
po_data = self._prepare_purchase_order(
line.request_id.picking_type_id,
line.request_id.group_id,
line.company_id,
line.request_id.origin,
)
po_data['demand_plan_line_id'] = item.line_id.demand_plan_line_id.id
# po_data.update({'related_product':line.related_product.id})
purchase = purchase_obj.create(po_data)
# Look for any other PO line in the selected PO with same
# product and UoM to sum quantities instead of creating a new
# po line
domain = self._get_order_line_search_domain(purchase, item)
available_po_lines = po_line_obj.search(domain)
new_pr_line = True
# If Unit of Measure is not set, update from wizard.
if not line.product_uom_id:
line.product_uom_id = item.product_uom_id
# Allocation UoM has to be the same as PR line UoM
alloc_uom = line.product_uom_id
wizard_uom = item.product_uom_id
if available_po_lines and not item.keep_description:
new_pr_line = False
po_line = available_po_lines[0]
po_line.purchase_request_lines = [(4, line.id)]
po_line.move_dest_ids |= line.move_dest_ids
po_line_product_uom_qty = po_line.product_uom._compute_quantity(
po_line.product_uom_qty, alloc_uom
)
wizard_product_uom_qty = wizard_uom._compute_quantity(
item.product_qty, alloc_uom
)
all_qty = min(po_line_product_uom_qty, wizard_product_uom_qty)
self.create_allocation(po_line, line, all_qty, alloc_uom)
else:
po_line_data = self._prepare_purchase_order_line(purchase, item)
if item.keep_description:
po_line_data["name"] = item.name
if line.related_product:
po_line_data.update({'related_product': line.related_product.id})
po_line = po_line_obj.create(po_line_data)
po_line_product_uom_qty = po_line.product_uom._compute_quantity(
po_line.product_uom_qty, alloc_uom
)
wizard_product_uom_qty = wizard_uom._compute_quantity(
item.product_qty, alloc_uom
)
all_qty = min(po_line_product_uom_qty, wizard_product_uom_qty)
self.create_allocation(po_line, line, all_qty, alloc_uom)
self._post_process_po_line(item, po_line, new_pr_line)
res.append(purchase.id)
purchase_requests = self.item_ids.mapped("request_id")
purchase_requests.button_in_progress()
return {
"domain": [("id", "in", res)],
"name": _("RFQ"),
"view_mode": "tree,form",
"res_model": "purchase.order",
"view_id": False,
"context": False,
"type": "ir.actions.act_window",
}

View File

@@ -10,13 +10,40 @@ class ReSaleOrder(models.Model):
string='与此销售订单相关联的制造订单', string='与此销售订单相关联的制造订单',
groups='mrp.group_mrp_user', store=True) groups='mrp.group_mrp_user', store=True)
demand_plan_ids = fields.Many2many(comodel_name="sf.demand.plan",
string="需求计划", readonly=True)
demand_plan_count = fields.Integer(
string="需求计划生成计数",
compute='_compute_demand_plan_count'
)
@api.depends('demand_plan_ids.line_ids.status')
def _compute_purchase_request_count(self):
for so in self:
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', so.name)])
if pr_ids:
so.purchase_request_purchase_order_count = len(pr_ids)
else:
so.purchase_request_purchase_order_count = 0
@api.depends('demand_plan_ids.line_ids')
def _compute_demand_plan_count(self):
for line in self:
demand_plan = self.env['sf.production.demand.plan'].sudo().search([('sale_order_id', '=', line.id)])
line.demand_plan_count = len(demand_plan)
def sale_order_create_line(self, product, item): def sale_order_create_line(self, product, item):
ret = super(ReSaleOrder, self).sale_order_create_line(product, item) ret = super(ReSaleOrder, self).sale_order_create_line(product, item)
vals = { vals = {
'sale_order_id': ret.order_id.id, 'sale_order_id': ret.order_id.id,
'sale_order_line_id': ret.id, 'sale_order_line_id': ret.id,
} }
demand_plan_info = self.env['sf.demand.plan'].sudo().create(vals)
vals.update({'demand_plan_id': demand_plan_info.id, 'plan_uom_qty': ret.product_uom_qty,
'new_supply_method': 'custom_made', 'custom_made_type': 'manual'})
demand_plan = self.env['sf.production.demand.plan'].sudo().create(vals) demand_plan = self.env['sf.production.demand.plan'].sudo().create(vals)
demand_plan_info.write({'line_ids': demand_plan.ids})
if demand_plan.product_id.machining_drawings_name: if demand_plan.product_id.machining_drawings_name:
filename_url = demand_plan.product_id.machining_drawings_name.rsplit('.', 1)[0] filename_url = demand_plan.product_id.machining_drawings_name.rsplit('.', 1)[0]
wizard_vals = { wizard_vals = {
@@ -26,4 +53,22 @@ class ReSaleOrder(models.Model):
'type': '1', 'type': '1',
} }
self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals) self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals)
ret.order_id.demand_plan_ids = [(4, demand_plan_info.id)]
return ret return ret
def confirm_to_supply_method(self):
self.state = 'sale'
for line in self.order_line:
if line.product_id.auto_machining:
line.supply_method = 'automation'
def action_view_demand_plan(self):
self.ensure_one()
demand_plan_ids = self.env['sf.production.demand.plan'].sudo().search([('sale_order_id', '=', self.id)]).ids
return {
'res_model': 'sf.production.demand.plan',
'type': 'ir.actions.act_window',
'name': _("需求计划"),
'domain': [('id', 'in', demand_plan_ids)],
'view_mode': 'tree',
}

View File

@@ -0,0 +1,212 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.tools import float_compare
from odoo.exceptions import ValidationError
class SfDemandPlan(models.Model):
_name = 'sf.demand.plan'
_description = 'sf_demand_plan'
state = fields.Selection([
('10', '需求确认'),
('20', '待工艺设计'),
('30', '部分下达'),
('40', '已下达'),
('50', '取消'),
], string='状态', default='10', compute='_compute_state', store=True)
line_ids = fields.One2many(comodel_name='sf.production.demand.plan',
inverse_name='demand_plan_id', string="需求计划", copy=True)
sale_order_id = fields.Many2one(comodel_name="sale.order",
string="销售订单", readonly=True)
sale_order_line_id = fields.Many2one(comodel_name="sale.order.line",
string="销售订单明细", readonly=True)
product_id = fields.Many2one(
comodel_name='product.product',
related='sale_order_line_id.product_id',
string='产品', store=True, index=True)
part_name = fields.Char('零件名称', related='product_id.part_name')
part_number = fields.Char('零件图号', compute='_compute_part_number', store=True)
materials_id = fields.Char('材料', compute='_compute_materials_id', store=True)
blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类',
related='product_id.blank_type')
embryo_long = fields.Char('坯料尺寸(mm)', compute='_compute_embryo_long', store=True)
is_incoming_material = fields.Boolean('客供料', related='sale_order_line_id.is_incoming_material', store=True)
pending_qty = fields.Float(
string="待计划",
compute='_compute_pending_qty', store=True)
planned_qty = fields.Float(
string="已计划",
compute='_compute_planned_qty', store=True)
model_id = fields.Char('模型ID', related='product_id.model_id')
customer_name = fields.Char('客户', related='sale_order_id.customer_name')
product_uom_qty = fields.Float(
string="需求数量",
related='sale_order_line_id.product_uom_qty', store=True)
deadline_of_delivery = fields.Date('客户交期', related='sale_order_line_id.delivery_end_date', store=True)
contract_date = fields.Date('合同日期', related='sale_order_id.contract_date')
contract_code = fields.Char('合同号', related='sale_order_id.contract_code', store=True)
model_process_parameters_ids = fields.Many2many('sf.production.process.parameter',
'demand_plan_process_parameter_rel',
string='表面工艺',
compute='_compute_model_process_parameters_ids'
, store=True
)
model_machining_precision = fields.Selection(related='product_id.model_machining_precision', string='精度')
inventory_quantity_auto_apply = fields.Float(
string="成品库存",
compute='_compute_inventory_quantity_auto_apply'
)
priority = fields.Selection([
('1', '紧急'),
('2', ''),
('3', ''),
('4', ''),
], string='优先级', default='3')
hide_button_release_plan = fields.Boolean(
string='显示下达计划按钮',
compute='_compute_hide_button_release_plan',
default=False
)
readonly_custom_made_type = fields.Boolean(
string='字段自制类型只读',
compute='_compute_readonly_custom_made_type',
default=False
)
@api.depends('product_id.part_number', 'product_id.model_name')
def _compute_part_number(self):
for line in self:
if line.product_id:
if line.product_id.part_number:
line.part_number = line.product_id.part_number
else:
if line.product_id.model_name:
line.part_number = line.product_id.model_name.rsplit('.', 1)[0]
else:
line.part_number = None
@api.depends('product_id.materials_id')
def _compute_materials_id(self):
for line in self:
if line.product_id:
line.materials_id = f"{line.product_id.materials_id.name}/{line.product_id.materials_type_id.name}"
else:
line.materials_id = None
@api.depends('product_id.model_long', 'product_id.model_width', 'product_id.model_height')
def _compute_embryo_long(self):
for line in self:
if line.product_id:
line.embryo_long = f"{round(line.product_id.model_long, 3)}*{round(line.product_id.model_width, 3)}*{round(line.product_id.model_height, 3)}"
else:
line.embryo_long = None
@api.depends('product_id.model_process_parameters_ids')
def _compute_model_process_parameters_ids(self):
for line in self:
if line.product_id and line.product_id.model_process_parameters_ids:
line.model_process_parameters_ids = [(6, 0, line.product_id.model_process_parameters_ids.ids)]
else:
line.model_process_parameters_ids = [(5, 0, 0)]
def _compute_inventory_quantity_auto_apply(self):
location_id = self.env['stock.location'].search([('name', '=', '成品存货区')], limit=1).id
product_ids = self.mapped('product_id').ids
if product_ids:
quant_data = self.env['stock.quant'].read_group(
domain=[
('product_id', 'in', product_ids),
('location_id', '=', location_id)
],
fields=['product_id', 'inventory_quantity_auto_apply'],
groupby=['product_id']
)
quantity_map = {item['product_id'][0]: item['inventory_quantity_auto_apply'] for item in quant_data}
else:
quantity_map = {}
for line in self:
if line.product_id:
line.inventory_quantity_auto_apply = quantity_map.get(line.product_id.id, 0.0)
else:
line.inventory_quantity_auto_apply = 0.0
@api.depends('product_uom_qty', 'line_ids.plan_uom_qty')
def _compute_pending_qty(self):
for line in self:
sum_plan_uom_qty = sum(line.line_ids.mapped('plan_uom_qty'))
pending_qty = line.product_uom_qty - sum_plan_uom_qty
rounding = line.product_id.uom_id.rounding or 0.01
if float_compare(pending_qty, 0,
precision_rounding=rounding) == -1:
line.pending_qty = 0
else:
line.pending_qty = pending_qty
@api.depends('line_ids.plan_uom_qty')
def _compute_planned_qty(self):
for line in self:
line.planned_qty = sum(line.line_ids.mapped('plan_uom_qty'))
@api.depends('line_ids.status')
def _compute_hide_button_release_plan(self):
for line in self:
line.hide_button_release_plan = bool(line.line_ids.filtered(
lambda p: p.status == '30'))
@api.depends('line_ids.status', 'sale_order_id.state')
def _compute_state(self):
for line in self:
status_line = line.line_ids.filtered(lambda p: p.status == '60')
if line.sale_order_id.state == 'cancel':
line.state = '50'
line.line_ids.status = '100'
elif len(line.line_ids) == len(status_line):
line.state = '40'
elif bool(status_line):
line.state = '30'
else:
line.state = '10'
@api.depends('line_ids.status')
def _compute_readonly_custom_made_type(self):
for line in self:
production_demand_plan = line.line_ids.filtered(
lambda p: p.status in ('50', '60') and p.new_supply_method == 'custom_made')
line.readonly_custom_made_type = bool(production_demand_plan)
def name_get(self):
result = []
for plan in self:
result.append((plan.id, plan.sale_order_id.name))
return result
def button_production_release_plan(self):
line_ids = self.line_ids.filtered(lambda p: p.status == '30')
sum_product_uom_qty = sum(line_ids.mapped('plan_uom_qty'))
if sum_product_uom_qty > self.product_uom_qty:
return {
'name': _('需求计划'),
'type': 'ir.actions.act_window',
'views': [(self.env.ref(
'sf_demand_plan.sf_release_plan_wizard_form').id,
'form')],
'res_model': 'sf.release.plan.wizard',
'target': 'new',
'context': {
'default_demand_plan_line_id': line_ids.ids,
'default_release_message': f"您正在下达计划量 {sum_product_uom_qty},需求数量为 {self.product_uom_qty},已超过需求数量,是否继续?",
}}
else:
for demand_plan_line_id in line_ids:
demand_plan_line_id.action_confirm()

File diff suppressed because it is too large Load Diff

View File

@@ -19,7 +19,7 @@ class SfStockRoute(models.Model):
[('supply_method', 'in', stock_route_group)]) [('supply_method', 'in', stock_route_group)])
if demand_plan_ids: if demand_plan_ids:
sr.demand_plan_ids = demand_plan_ids.ids sr.demand_plan_ids = demand_plan_ids.ids
break continue
sr.demand_plan_ids = None sr.demand_plan_ids = None
# def name_get(self): # def name_get(self):

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class StockRule(models.Model):
_inherit = 'stock.rule'
def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values,
bom):
res = super()._prepare_mo_vals(product_id, product_qty, product_uom, location_id, name, origin, company_id,
values, bom)
if self.env.context.get('demand_plan_line_id'):
res['demand_plan_line_id'] = self.env.context.get('demand_plan_line_id')
return res
@api.model
def _prepare_purchase_request_line(self, request_id, procurement):
res = super()._prepare_purchase_request_line(request_id, procurement)
if self.env.context.get('demand_plan_line_id'):
res['demand_plan_line_id'] = self.env.context.get('demand_plan_line_id')
return res

View File

@@ -1,9 +1,16 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sf_production_demand_plan,sf.production.demand.plan,model_sf_production_demand_plan,base.group_user,1,0,0,0 access_sf_production_demand_plan,sf.production.demand.plan,model_sf_production_demand_plan,base.group_user,1,0,0,0
access_sf_production_demand_plan_for_dispatch,sf.production.demand.plan for dispatch,model_sf_production_demand_plan,sf_base.group_plan_dispatch,1,1,0,0 access_sf_production_demand_plan_for_dispatch,sf.production.demand.plan for dispatch,model_sf_production_demand_plan,sf_base.group_plan_dispatch,1,1,1,1
access_sf_demand_plan_print_wizard,sf.demand.plan.print.wizard,model_sf_demand_plan_print_wizard,base.group_user,1,0,0,0 access_sf_demand_plan_print_wizard,sf.demand.plan.print.wizard,model_sf_demand_plan_print_wizard,base.group_user,1,0,0,0
access_sf_demand_plan_print_wizard_for_dispatch,sf.demand.plan.print.wizard for dispatch,model_sf_demand_plan_print_wizard,sf_base.group_plan_dispatch,1,1,0,0 access_sf_demand_plan_print_wizard_for_dispatch,sf.demand.plan.print.wizard for dispatch,model_sf_demand_plan_print_wizard,sf_base.group_plan_dispatch,1,1,0,0
access_sf_demand_plan,sf.demand.plan,model_sf_demand_plan,base.group_user,1,0,0,0
access_sf_demand_plan_for_dispatch,sf.demand.plan for dispatch,model_sf_demand_plan,sf_base.group_plan_dispatch,1,1,0,0
access_stock_route_group,stock.route.group,model_stock_route_group,base.group_user,1,0,0,0 access_stock_route_group,stock.route.group,model_stock_route_group,base.group_user,1,0,0,0
access_stock_route_group_dispatch,stock.route.group.dispatch,model_stock_route_group,sf_base.group_plan_dispatch,1,1,0,0 access_stock_route_group_dispatch,stock.route.group.dispatch,model_stock_route_group,sf_base.group_plan_dispatch,1,1,0,0
access_sf_release_plan_wizard,sf.release.plan.wizard,model_sf_release_plan_wizard,base.group_user,1,0,0,0
access_sf_release_plan_wizard_for_dispatch,sf.release.plan.wizard for dispatch,model_sf_release_plan_wizard,sf_base.group_plan_dispatch,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sf_production_demand_plan sf.production.demand.plan model_sf_production_demand_plan base.group_user 1 0 0 0
3 access_sf_production_demand_plan_for_dispatch sf.production.demand.plan for dispatch model_sf_production_demand_plan sf_base.group_plan_dispatch 1 1 0 1 0 1
4 access_sf_demand_plan_print_wizard sf.demand.plan.print.wizard model_sf_demand_plan_print_wizard base.group_user 1 0 0 0
5 access_sf_demand_plan_print_wizard_for_dispatch sf.demand.plan.print.wizard for dispatch model_sf_demand_plan_print_wizard sf_base.group_plan_dispatch 1 1 0 0
6 access_stock_route_group access_sf_demand_plan stock.route.group sf.demand.plan model_stock_route_group model_sf_demand_plan base.group_user 1 0 0 0
7 access_stock_route_group_dispatch access_sf_demand_plan_for_dispatch stock.route.group.dispatch sf.demand.plan for dispatch model_stock_route_group model_sf_demand_plan sf_base.group_plan_dispatch 1 1 0 0
8 access_stock_route_group stock.route.group model_stock_route_group base.group_user 1 0 0 0
9 access_stock_route_group_dispatch stock.route.group.dispatch model_stock_route_group sf_base.group_plan_dispatch 1 1 0 0
10 access_sf_release_plan_wizard sf.release.plan.wizard model_sf_release_plan_wizard base.group_user 1 0 0 0
11 access_sf_release_plan_wizard_for_dispatch sf.release.plan.wizard for dispatch model_sf_release_plan_wizard sf_base.group_plan_dispatch 1 1 1 1
12
13
14
15
16

View File

@@ -4,10 +4,13 @@
<field name="model">sf.production.demand.plan</field> <field name="model">sf.production.demand.plan</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="需求计划" default_order="sequence desc,id desc" editable="bottom" <tree string="需求计划" default_order="sequence desc,id desc" editable="bottom"
class="demand_plan_tree freeze-columns-before-part_number"> class="demand_plan_tree freeze-columns-before-part_number" create="false" delete="false">
<header> <header>
<button string="打印" name="button_action_print" type="object" <button string="打印" name="button_action_print" type="object"
class="btn-primary"/> class="btn-primary"/>
<button string="下达计划" name="button_batch_release_plan" type="object"
class="btn-primary"
/>
</header> </header>
<field name="sequence" widget="handle"/> <field name="sequence" widget="handle"/>
<field name="id" optional="hide"/> <field name="id" optional="hide"/>
@@ -20,8 +23,13 @@
<field name="part_name"/> <field name="part_name"/>
<field name="part_number"/> <field name="part_number"/>
<field name="is_incoming_material"/> <field name="is_incoming_material"/>
<field name="supply_method"/> <field name="new_supply_method" attrs="{'readonly': [('status', '!=', '30')]}"/>
<field name="readonly_custom_made_type" invisible="1"/>
<field name="custom_made_type"
attrs="{'readonly': ['|',('status', '!=', '30'),('readonly_custom_made_type', '=', True)],
'required': [('new_supply_method', '=', 'custom_made')]}"/>
<field name="product_uom_qty"/> <field name="product_uom_qty"/>
<field name="plan_uom_qty" attrs="{'readonly': [('status', '!=', '30')]}"/>
<field name="deadline_of_delivery"/> <field name="deadline_of_delivery"/>
<field name="inventory_quantity_auto_apply"/> <field name="inventory_quantity_auto_apply"/>
<field name="qty_delivered"/> <field name="qty_delivered"/>
@@ -38,32 +46,32 @@
<field name="sale_order_id" optional="hide"/> <field name="sale_order_id" optional="hide"/>
<field name="sale_order_line_number" optional="hide"/> <field name="sale_order_line_number" optional="hide"/>
<field name="order_state"/> <field name="order_state"/>
<field name="route_ids" widget="many2many_tags" optional="hide" readonly="0" <field name="route_ids" widget="many2many_tags" optional="hide"
context="{'demand_plan_search_stock_route_id': id}"/> context="{'demand_plan_search_stock_route_id': id}"/>
<field name="contract_date"/> <field name="contract_date"/>
<field name="date_order"/> <field name="date_order"/>
<field name="contract_code"/> <field name="contract_code"/>
<field name="plan_remark"/> <field name="plan_remark" attrs="{'readonly': [('status', 'in', ('60','100'))]}"/>
<field name="priority" decoration-danger="priority == '1'" <field name="priority" decoration-danger="priority == '1'"
decoration-warning="priority == '2'" decoration-warning="priority == '2'"
decoration-info="priority == '3'" decoration-info="priority == '3'"
decoration-success="priority == '4'"/> decoration-success="priority == '4'"/>
<field name="material_check" optional="hide"/> <field name="material_check" optional="hide"/>
<field name="hide_action_open_mrp_production" invisible="1"/> <!-- <field name="hide_action_open_mrp_production" invisible="1"/>-->
<field name="hide_action_purchase_orders" invisible="1"/> <!-- <field name="hide_action_purchase_orders" invisible="1"/>-->
<field name="hide_action_stock_picking" invisible="1"/> <!-- <field name="hide_action_stock_picking" invisible="1"/>-->
<field name="hide_action_view_programming" invisible="1"/> <!-- <field name="hide_action_view_programming" invisible="1"/>-->
<button name="action_open_sale_order" type="object" string="供货方式待确认" class="btn-secondary" <!-- <button name="action_open_sale_order" type="object" string="供货方式待确认" class="btn-secondary"-->
attrs="{'invisible': [('supply_method', '!=', False)]}"/> <!-- attrs="{'invisible': [('supply_method', '!=', False)]}"/>-->
<button name="action_open_mrp_production" type="object" string="待工艺确认" class="btn-secondary" <!-- <button name="action_open_mrp_production" type="object" string="待工艺确认" class="btn-secondary"-->
attrs="{'invisible': [('hide_action_open_mrp_production', '=', False)]}"/> <!-- attrs="{'invisible': [('hide_action_open_mrp_production', '=', False)]}"/>-->
<button name="action_view_purchase_request" type="object" string="采购申请" class="btn-secondary" <!-- <button name="action_view_purchase_request" type="object" string="采购申请" class="btn-secondary"-->
attrs="{'invisible': [('hide_action_purchase_orders', '=', False)]}"/> <!-- attrs="{'invisible': [('hide_action_purchase_orders', '=', False)]}"/>-->
<button name="action_view_stock_picking" type="object" string="调拨单" class="btn-secondary" <!-- <button name="action_view_stock_picking" type="object" string="调拨单" class="btn-secondary"-->
attrs="{'invisible': [('hide_action_stock_picking', '=', False)]}"/> <!-- attrs="{'invisible': [('hide_action_stock_picking', '=', False)]}"/>-->
<button name="action_view_programming" type="object" string="编程单" class="btn-secondary" <!-- <button name="action_view_programming" type="object" string="编程单" class="btn-secondary"-->
attrs="{'invisible': [('hide_action_view_programming', '=', False)]}"/> <!-- attrs="{'invisible': [('hide_action_view_programming', '=', False)]}"/>-->
<field name="planned_start_date"/> <field name="planned_start_date" attrs="{'readonly': [('status', 'in', ('60','100'))]}"/>
<field name="actual_start_date"/> <field name="actual_start_date"/>
<field name="actual_end_date"/> <field name="actual_end_date"/>
<field name="processing_time"/> <field name="processing_time"/>
@@ -72,8 +80,15 @@
<field name="write_date" string="更新时间"/> <field name="write_date" string="更新时间"/>
<field name="write_uid" optional="hide" string="更新人"/> <field name="write_uid" optional="hide" string="更新人"/>
<field name="print_count"/> <field name="print_count"/>
<button name="release_production_order" type="object" string="下达生产" class="btn-primary" <field name="hide_release_production_order" invisible="1"/>
attrs="{'invisible': ['|',('status', '!=', '50'), ('supply_method', 'not in', ['automation', 'manual'])]}"/> <button string="下达计划" name="button_release_plan" type="object"
class="btn-primary"
attrs="{'invisible': [('status', 'in', ('50','60','100'))]}"
/>
<button name="button_release_production" type="object" string="下发生产" class="btn-primary"
attrs="{'invisible': [('hide_release_production_order', '=', False)]}"
/>
<button name="edit_button" type="object" string="拆分" class="btn-primary"/>
</tree> </tree>
</field> </field>
</record> </record>
@@ -120,17 +135,4 @@
<field name="view_mode">tree</field> <field name="view_mode">tree</field>
</record> </record>
<menuitem
id="demand_plan_menu"
name="需求计划"
sequence="140"
action="sf_production_demand_plan_action"
parent="sf_plan.sf_production_plan_menu"
/>
<!-- 调拨动作中屏蔽验证-->
<record id="stock.action_validate_picking" model="ir.actions.server">
<field name="binding_model_id" eval="False"/>
</record>
</odoo> </odoo>

View File

@@ -0,0 +1,85 @@
<odoo>
<record id="view_sf_demand_plan_form" model="ir.ui.view">
<field name="name">sf.demand.plan.form</field>
<field name="model">sf.demand.plan</field>
<field name="arch" type="xml">
<form>
<header>
<field name="state" widget="statusbar"/>
<field name="hide_button_release_plan" invisible="1"/>
<button string="下达计划" name="button_production_release_plan" type="object"
class="btn-primary"
attrs="{'invisible': [('hide_button_release_plan', '=', False)]}"/>
</header>
<sheet>
<group>
<group>
<field name="product_id"/>
<field name="part_name"/>
<field name="part_number"/>
<field name="materials_id"/>
<field name="blank_type"/>
<field name="embryo_long"/>
<field name="is_incoming_material"/>
<field name="pending_qty"/>
<field name="planned_qty"/>
<field name="model_id"/>
</group>
<group>
<field name="customer_name"/>
<field name="product_uom_qty"/>
<field name="deadline_of_delivery"/>
<field name="contract_date"/>
<field name="contract_code"/>
<field name="model_process_parameters_ids" widget="many2many_tags"/>
<field name="model_machining_precision"/>
<field name="inventory_quantity_auto_apply"/>
<field name="priority" attrs="{'readonly': [('state', 'in', ('40','50'))]}"/>
</group>
</group>
<notebook>
<page string="计划">
<field name="line_ids" attrs="{'readonly': [('state', 'in', ('40','50'))]}">
<tree editable="bottom" delete="false">
<field name="status"/>
<field name="readonly_custom_made_type" invisible="1"/>
<field name="new_supply_method" attrs="{'readonly': [('status', '!=', '30')]}"/>
<field name="custom_made_type"
attrs="{
'readonly': ['|', '|', ('new_supply_method', '!=', 'custom_made'), ('status', '!=', '30'), ('readonly_custom_made_type', '=', True)],
'required': [('new_supply_method', '=', 'custom_made')]}"/>
<field name="route_ids" widget="many2many_tags" optional="hide"/>
<field name="location_id" optional="hide"/>
<field name="bom_id" optional="hide"/>
<field name="plan_uom_qty" attrs="{'readonly': [('status', '!=', '30')]}"/>
<field name="blank_arrival_date"/>
<field name="finished_product_arrival_date"/>
<field name="planned_start_date"/>
<field name="actual_start_date"/>
<field name="actual_end_date"/>
<field name="plan_remark"/>
<field name="procurement_reason"/>
<field name="write_date" string="更新时间"/>
<field name="hide_release_production_order" invisible="1"/>
<button string="下达计划" name="button_release_plan" type="object"
class="btn-primary"
attrs="{'invisible': [('status', 'in', ('50','60','100'))]}"
/>
<button name="button_release_production" type="object" string="下发生产"
class="btn-primary"
attrs="{'invisible': [('hide_release_production_order', '=', False)]}"
/>
<button name="button_delete" type="object" string="删除"
class="btn-primary"
attrs="{'invisible': [('status', 'not in', ('10','20','30'))]}"
confirm='是否确认删除?'/>
</tree>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<menuitem
id="demand_plan_menu"
name="需求计划"
sequence="140"
action="sf_demand_plan.sf_production_demand_plan_action"
parent="sf_plan.sf_production_plan_menu"
/>
<!-- 调拨动作中屏蔽验证-->
<record id="stock.action_validate_picking" model="ir.actions.server">
<field name="binding_model_id" eval="False"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_order_form_inherit_plan" model="ir.ui.view">
<field name="name">view.sale.order.form.inherit.plan</field>
<field name="inherit_id" ref="sf_manufacturing.view_order_form_inherit_supply_method"/>
<field name="model">sale.order</field>
<field name="arch" type="xml">
<xpath expr="//header/button[@name='action_confirm'][last()]" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
<xpath expr="//page/field[@name='order_line']/tree/field[@name='supply_method']" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
<xpath expr="//div[@name='button_box']" position="inside">
<button class="oe_stat_button" name="action_view_demand_plan" type="object" icon="fa-pencil-square-o"
attrs="{'invisible': [('demand_plan_count', '=', 0)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="demand_plan_count"/>
</span>
<span class="o_stat_text">需求计划</span>
</div>
</button>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1 +1,2 @@
from . import sf_demand_plan_print_wizard from . import sf_demand_plan_print_wizard
from . import sf_release_plan_wizard

View File

@@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
import logging
from odoo import models, fields, api, _
_logger = logging.getLogger(__name__)
class SfReleasePlanWizard(models.TransientModel):
_name = 'sf.release.plan.wizard'
_description = u'下达计划向导'
demand_plan_line_id = fields.Many2many(comodel_name="sf.production.demand.plan",
string="需求计划明细", readonly=True)
release_message = fields.Char(string='提示', readonly=True)
def confirm(self):
if self.demand_plan_line_id:
for demand_plan_line_id in self.demand_plan_line_id:
demand_plan_line_id.action_confirm()

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.ui.view" id="sf_release_plan_wizard_form">
<field name="name">sf.release.plan.wizard.form</field>
<field name="model">sf.release.plan.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<div>
<div style="white-space: pre-wrap;">
<field name="release_message"/>
</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>
</odoo>

View File

@@ -44,7 +44,8 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
if productions: if productions:
# 修改需求计划中的程序工时 # 修改需求计划中的程序工时
demand_plan = request.env['sf.production.demand.plan'].with_user( demand_plan = request.env['sf.production.demand.plan'].with_user(
request.env.ref("base.user_admin")).search([('model_id', '=', ret['folder_name'])]) request.env.ref("base.user_admin")).search(
[('model_id', '=', ret['folder_name']), ('new_supply_method', '=', 'custom_made')])
if demand_plan and ret['total_estimated_time']: if demand_plan and ret['total_estimated_time']:
demand_plan.write( demand_plan.write(
{'processing_time': ret['total_estimated_time']}) {'processing_time': ret['total_estimated_time']})

View File

@@ -22,6 +22,7 @@
'data/report_actions.xml', 'data/report_actions.xml',
'views/view.xml', 'views/view.xml',
'views/quality_cnc_test_view.xml', 'views/quality_cnc_test_view.xml',
'views/stock_picking.xml',
'views/mrp_workorder.xml', 'views/mrp_workorder.xml',
'views/quality_check_view.xml', 'views/quality_check_view.xml',
'views/quality_company.xml', 'views/quality_company.xml',

View File

@@ -1,24 +1,29 @@
import logging import logging
from odoo import api, models from odoo import api, models, fields
from odoo.exceptions import ValidationError, UserError from odoo.exceptions import ValidationError, UserError
class StockPicking(models.Model): class StockPicking(models.Model):
_inherit = 'stock.picking' _inherit = 'stock.picking'
whether_show_quality_check = fields.Boolean('是否显示质量检测按钮', default=True)
def _compute_check(self): def _compute_check(self):
super()._compute_check() super()._compute_check()
for picking in self: for picking in self:
picking_to_quality = picking.get_picking_to_quality() picking_to_quality = picking.get_picking_to_quality()
if not picking_to_quality: if not picking_to_quality:
picking.quality_check_todo = False picking.quality_check_todo = False
picking.whether_show_quality_check = False
break break
else: else:
picking.whether_show_quality_check = True
need_quality_line = picking.get_need_quality_line(picking_to_quality) need_quality_line = picking.get_need_quality_line(picking_to_quality)
if not need_quality_line or all(not line.get('need_done_check_ids') for line in need_quality_line): if not need_quality_line or all(not line.get('need_done_check_ids') for line in need_quality_line):
picking.quality_check_todo = False picking.quality_check_todo = False
def check_quality(self): def check_quality(self):
self.ensure_one() self.ensure_one()
# checkable_products = self.mapped('move_line_ids').mapped('product_id') # checkable_products = self.mapped('move_line_ids').mapped('product_id')

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="stock_picking_view_form_inherit_quality_sf_quality" model="ir.ui.view">
<field name="name">stock.picking.view.form.sf.quality</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="quality_control.stock_picking_view_form_inherit_quality"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']" position="inside">
<field name="whether_show_quality_check" invisible="1"/>
</xpath>
<xpath expr="//button[@name='action_open_quality_check_picking'][1]" position="attributes">
<attribute name="attrs">{'invisible': ['|', '|','|', ('check_ids', '=', []), ('quality_check_fail', '=',
True), ('quality_check_todo', '!=', True), ('whether_show_quality_check', '!=', True)]}
</attribute>
</xpath>
<xpath expr="//button[@name='action_open_quality_check_picking'][2]" position="attributes">
<attribute name="attrs">{'invisible': ['|', '|','|', ('check_ids', '=', []), ('quality_check_fail', '=',
True), ('quality_check_todo', '=', True), ('whether_show_quality_check', '!=', True)]}
</attribute>
</xpath>
<xpath expr="//button[@name='action_open_quality_check_picking'][3]" position="attributes">
<attribute name="attrs">{'invisible': ['|', '|',('check_ids', '=', []), ('quality_check_fail', '!=',
True), ('whether_show_quality_check', '!=', True)]}
</attribute>
</xpath>
</field>
</record>
</odoo>

View File

@@ -16,6 +16,7 @@
# 'security/sf_stock_security.xml', # 'security/sf_stock_security.xml',
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'wizard/wizard_view.xml', 'wizard/wizard_view.xml',
'views/product.xml',
'views/view.xml', 'views/view.xml',
'views/shelf_location.xml', 'views/shelf_location.xml',
'views/change_stock_move_views.xml', 'views/change_stock_move_views.xml',

View File

@@ -1,2 +1,3 @@
from . import model from . import model
from . import sync_common from . import sync_common
from . import product

View File

@@ -471,7 +471,7 @@ class ShelfLocation(models.Model):
record.display_rfid = record.product_sn_id.rfid if record.product_sn_id else '' record.display_rfid = record.product_sn_id.rfid if record.product_sn_id else ''
except Exception as e: except Exception as e:
record.display_rfid = '' record.display_rfid = ''
@api.depends('product_id') @api.depends('product_id')
def _compute_tool(self): def _compute_tool(self):
"""计算工具 RFID""" """计算工具 RFID"""
@@ -594,7 +594,7 @@ class ShelfLocation(models.Model):
_layer_capacity = _cc_code % record.shelf_id.layer_capacity _layer_capacity = _cc_code % record.shelf_id.layer_capacity
if _layer_capacity == 0: if _layer_capacity == 0:
_layer_capacity = record.shelf_id.layer_capacity _layer_capacity = record.shelf_id.layer_capacity
else: else:
_layer_capacity = _layer_capacity _layer_capacity = _layer_capacity
_layer = _layer+1 _layer = _layer+1
_layer_capacity = f"{_layer_capacity:02d}" _layer_capacity = f"{_layer_capacity:02d}"
@@ -634,7 +634,7 @@ class SfShelfLocationLot(models.Model):
for item in self: for item in self:
if item.qty_num > item.qty: if item.qty_num > item.qty:
raise ValidationError('变更数量不能比库存数量大!!!') raise ValidationError('变更数量不能比库存数量大!!!')
class SfStockMoveLine(models.Model): class SfStockMoveLine(models.Model):
@@ -899,7 +899,7 @@ class SfStockMoveLine(models.Model):
def _compute_current_location_id(self): def _compute_current_location_id(self):
# 批量获取所有相关记录的picking # 批量获取所有相关记录的picking
pickings = self.mapped('picking_id') pickings = self.mapped('picking_id')
# 构建源picking的移库行与目标位置的映射 # 构建源picking的移库行与目标位置的映射
origin_location_map = {} origin_location_map = {}
for picking in pickings: for picking in pickings:
@@ -907,11 +907,11 @@ class SfStockMoveLine(models.Model):
origin_move = picking.move_ids[:1].move_orig_ids[:1] origin_move = picking.move_ids[:1].move_orig_ids[:1]
if not origin_move: if not origin_move:
continue continue
origin_picking = origin_move.picking_id origin_picking = origin_move.picking_id
if not origin_picking: if not origin_picking:
continue continue
# 为每个picking构建lot_id到location的映射 # 为每个picking构建lot_id到location的映射
origin_location_map[picking.id] = { origin_location_map[picking.id] = {
move_line.lot_id.id: move_line.destination_location_id move_line.lot_id.id: move_line.destination_location_id
@@ -919,17 +919,17 @@ class SfStockMoveLine(models.Model):
lambda ml: ml.destination_location_id and ml.lot_id lambda ml: ml.destination_location_id and ml.lot_id
) )
} }
# 批量更新current_location_id # 批量更新current_location_id
for record in self: for record in self:
current_picking = record.picking_id current_picking = record.picking_id
if not current_picking: if not current_picking:
record.current_location_id = False record.current_location_id = False
continue continue
# 获取当前picking对应的lot_location映射 # 获取当前picking对应的lot_location映射
lot_dest_map = origin_location_map.get(current_picking.id, {}) lot_dest_map = origin_location_map.get(current_picking.id, {})
# 查找匹配的lot_id # 查找匹配的lot_id
for move_line in current_picking.move_line_ids: for move_line in current_picking.move_line_ids:
if move_line.lot_id and move_line.lot_id.id in lot_dest_map: if move_line.lot_id and move_line.lot_id.id in lot_dest_map:
@@ -1082,6 +1082,10 @@ class SfStockPicking(models.Model):
重写验证方法,当验证时意味着调拨单已经完成,已经移动到了目标货位,所以需要将当前货位的状态改为空闲 重写验证方法,当验证时意味着调拨单已经完成,已经移动到了目标货位,所以需要将当前货位的状态改为空闲
""" """
res = super(SfStockPicking, self).button_validate() res = super(SfStockPicking, self).button_validate()
if any(ml.state == 'done' for ml in self.move_line_ids):
# 验证产品库存为负库存问题
self.move_ids.product_id.verify_product_repertory(self.location_id)
for line in self.move_line_ids: for line in self.move_line_ids:
if line: if line:
if line.destination_location_id: if line.destination_location_id:

View File

@@ -0,0 +1,29 @@
from odoo import models, fields
from odoo.exceptions import ValidationError
class SfProductCategory(models.Model):
_inherit = 'product.category'
negative_inventory_allowed = fields.Boolean('可负库存', default=True)
class SfProductTemplate(models.Model):
_inherit = 'product.product'
def verify_product_repertory(self, location_id):
"""
验证产品 负库存
"""
if not location_id:
raise ValidationError('当前位置为空!!')
elif len(location_id) != 1:
raise ValidationError(f'存在多个当前位置{[item.name for item in location_id]}')
elif location_id.usage == 'supplier':
return True
for pp in self:
if not pp.categ_id.negative_inventory_allowed:
sq = pp.stock_quant_ids.filtered(lambda sq: sq.quantity < 0 and sq.location_id == location_id)
if sq:
raise ValidationError(
f'产品{pp.name}的产品类型设置为不可负库存,当前操作会导致产品{pp.name}在库存{location_id.name}上的库存数量为负!!!')

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="product_category_form_view_sf_warehouse" model="ir.ui.view">
<field name="name">product.category.property.form.warehouse</field>
<field name="model">product.category</field>
<field name="inherit_id" ref="account.view_category_property_form"/>
<field name="arch" type="xml">
<xpath expr="//group[@name='account_property']" position="after">
<group name="other">
<group string="其他">
<field name="negative_inventory_allowed"/>
</group>
</group>
</xpath>
</field>
</record>
</odoo>