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
This commit is contained in:
@@ -44,7 +44,7 @@ class StatusChange(models.Model):
|
||||
else:
|
||||
action.update({
|
||||
'name': _("从 %s生成采购请求单", self.name),
|
||||
'domain': [('id', 'in', pr_ids)],
|
||||
'domain': [('id', 'in', pr_ids.ids)],
|
||||
'view_mode': 'tree,form',
|
||||
})
|
||||
return action
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
{
|
||||
'name': '机企猫智能工厂 需求计划',
|
||||
'version': '1.0',
|
||||
'version': '1.1',
|
||||
'summary': '智能工厂计划管理',
|
||||
'sequence': 1,
|
||||
'description': """
|
||||
@@ -14,9 +14,13 @@
|
||||
'data': [
|
||||
'security/ir.model.access.csv',
|
||||
'data/stock_route_group.xml',
|
||||
'views/demand_plan_info.xml',
|
||||
'views/demand_plan.xml',
|
||||
'views/stock_route.xml',
|
||||
'views/sale_order_views.xml',
|
||||
'wizard/sf_demand_plan_print_wizard_view.xml',
|
||||
'wizard/sf_release_plan_wizard_views.xml',
|
||||
'views/menu_view.xml',
|
||||
],
|
||||
'demo': [
|
||||
],
|
||||
|
||||
25
sf_demand_plan/migrations/1.1/post-migrate.py
Normal file
25
sf_demand_plan/migrations/1.1/post-migrate.py
Normal 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})
|
||||
@@ -1,5 +1,11 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
from . import sf_demand_plan
|
||||
from . import sf_production_demand_plan
|
||||
from . import sale_order
|
||||
from . import stock_route
|
||||
from . import mrp_bom
|
||||
from . import mrp_production
|
||||
from . import stock_rule
|
||||
from . import purchase_request
|
||||
from . import purchase_order
|
||||
|
||||
20
sf_demand_plan/models/mrp_bom.py
Normal file
20
sf_demand_plan/models/mrp_bom.py
Normal 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
|
||||
43
sf_demand_plan/models/mrp_production.py
Normal file
43
sf_demand_plan/models/mrp_production.py
Normal 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)
|
||||
44
sf_demand_plan/models/purchase_order.py
Normal file
44
sf_demand_plan/models/purchase_order.py
Normal 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
|
||||
110
sf_demand_plan/models/purchase_request.py
Normal file
110
sf_demand_plan/models/purchase_request.py
Normal 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",
|
||||
}
|
||||
@@ -10,13 +10,40 @@ class ReSaleOrder(models.Model):
|
||||
string='与此销售订单相关联的制造订单',
|
||||
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):
|
||||
ret = super(ReSaleOrder, self).sale_order_create_line(product, item)
|
||||
vals = {
|
||||
'sale_order_id': ret.order_id.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_info.write({'line_ids': demand_plan.ids})
|
||||
if demand_plan.product_id.machining_drawings_name:
|
||||
filename_url = demand_plan.product_id.machining_drawings_name.rsplit('.', 1)[0]
|
||||
wizard_vals = {
|
||||
@@ -26,4 +53,22 @@ class ReSaleOrder(models.Model):
|
||||
'type': '1',
|
||||
}
|
||||
self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals)
|
||||
ret.order_id.demand_plan_ids = [(4, demand_plan_info.id)]
|
||||
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',
|
||||
}
|
||||
|
||||
214
sf_demand_plan/models/sf_demand_plan.py
Normal file
214
sf_demand_plan/models/sf_demand_plan.py
Normal file
@@ -0,0 +1,214 @@
|
||||
# -*- 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
|
||||
if float_compare(pending_qty, 0,
|
||||
precision_rounding=line.product_id.uom_id.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'))
|
||||
|
||||
def button_release_plan(self):
|
||||
pass
|
||||
|
||||
@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
@@ -19,7 +19,7 @@ class SfStockRoute(models.Model):
|
||||
[('supply_method', 'in', stock_route_group)])
|
||||
if demand_plan_ids:
|
||||
sr.demand_plan_ids = demand_plan_ids.ids
|
||||
break
|
||||
continue
|
||||
sr.demand_plan_ids = None
|
||||
|
||||
# def name_get(self):
|
||||
|
||||
22
sf_demand_plan/models/stock_rule.py
Normal file
22
sf_demand_plan/models/stock_rule.py
Normal 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
|
||||
@@ -1,9 +1,16 @@
|
||||
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_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_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_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
|
||||
|
@@ -4,10 +4,13 @@
|
||||
<field name="model">sf.production.demand.plan</field>
|
||||
<field name="arch" type="xml">
|
||||
<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>
|
||||
<button string="打印" name="button_action_print" type="object"
|
||||
class="btn-primary"/>
|
||||
<button string="下达计划" name="button_batch_release_plan" type="object"
|
||||
class="btn-primary"
|
||||
/>
|
||||
</header>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="id" optional="hide"/>
|
||||
@@ -20,8 +23,13 @@
|
||||
<field name="part_name"/>
|
||||
<field name="part_number"/>
|
||||
<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="plan_uom_qty" attrs="{'readonly': [('status', '!=', '30')]}"/>
|
||||
<field name="deadline_of_delivery"/>
|
||||
<field name="inventory_quantity_auto_apply"/>
|
||||
<field name="qty_delivered"/>
|
||||
@@ -38,32 +46,32 @@
|
||||
<field name="sale_order_id" optional="hide"/>
|
||||
<field name="sale_order_line_number" optional="hide"/>
|
||||
<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}"/>
|
||||
<field name="contract_date"/>
|
||||
<field name="date_order"/>
|
||||
<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'"
|
||||
decoration-warning="priority == '2'"
|
||||
decoration-info="priority == '3'"
|
||||
decoration-success="priority == '4'"/>
|
||||
<field name="material_check" optional="hide"/>
|
||||
<field name="hide_action_open_mrp_production" invisible="1"/>
|
||||
<field name="hide_action_purchase_orders" invisible="1"/>
|
||||
<field name="hide_action_stock_picking" invisible="1"/>
|
||||
<field name="hide_action_view_programming" invisible="1"/>
|
||||
<button name="action_open_sale_order" type="object" string="供货方式待确认" class="btn-secondary"
|
||||
attrs="{'invisible': [('supply_method', '!=', False)]}"/>
|
||||
<button name="action_open_mrp_production" type="object" string="待工艺确认" class="btn-secondary"
|
||||
attrs="{'invisible': [('hide_action_open_mrp_production', '=', False)]}"/>
|
||||
<button name="action_view_purchase_request" type="object" string="采购申请" class="btn-secondary"
|
||||
attrs="{'invisible': [('hide_action_purchase_orders', '=', False)]}"/>
|
||||
<button name="action_view_stock_picking" type="object" string="调拨单" class="btn-secondary"
|
||||
attrs="{'invisible': [('hide_action_stock_picking', '=', False)]}"/>
|
||||
<button name="action_view_programming" type="object" string="编程单" class="btn-secondary"
|
||||
attrs="{'invisible': [('hide_action_view_programming', '=', False)]}"/>
|
||||
<field name="planned_start_date"/>
|
||||
<!-- <field name="hide_action_open_mrp_production" invisible="1"/>-->
|
||||
<!-- <field name="hide_action_purchase_orders" invisible="1"/>-->
|
||||
<!-- <field name="hide_action_stock_picking" invisible="1"/>-->
|
||||
<!-- <field name="hide_action_view_programming" invisible="1"/>-->
|
||||
<!-- <button name="action_open_sale_order" type="object" string="供货方式待确认" class="btn-secondary"-->
|
||||
<!-- attrs="{'invisible': [('supply_method', '!=', False)]}"/>-->
|
||||
<!-- <button name="action_open_mrp_production" type="object" string="待工艺确认" class="btn-secondary"-->
|
||||
<!-- attrs="{'invisible': [('hide_action_open_mrp_production', '=', False)]}"/>-->
|
||||
<!-- <button name="action_view_purchase_request" type="object" string="采购申请" class="btn-secondary"-->
|
||||
<!-- attrs="{'invisible': [('hide_action_purchase_orders', '=', False)]}"/>-->
|
||||
<!-- <button name="action_view_stock_picking" type="object" string="调拨单" class="btn-secondary"-->
|
||||
<!-- attrs="{'invisible': [('hide_action_stock_picking', '=', False)]}"/>-->
|
||||
<!-- <button name="action_view_programming" type="object" string="编程单" class="btn-secondary"-->
|
||||
<!-- attrs="{'invisible': [('hide_action_view_programming', '=', False)]}"/>-->
|
||||
<field name="planned_start_date" attrs="{'readonly': [('status', 'in', ('60','100'))]}"/>
|
||||
<field name="actual_start_date"/>
|
||||
<field name="actual_end_date"/>
|
||||
<field name="processing_time"/>
|
||||
@@ -72,8 +80,15 @@
|
||||
<field name="write_date" string="更新时间"/>
|
||||
<field name="write_uid" optional="hide" string="更新人"/>
|
||||
<field name="print_count"/>
|
||||
<button name="release_production_order" type="object" string="下达生产" class="btn-primary"
|
||||
attrs="{'invisible': ['|',('status', '!=', '50'), ('supply_method', 'not in', ['automation', 'manual'])]}"/>
|
||||
<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="edit_button" type="object" string="拆分" class="btn-primary"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
@@ -120,17 +135,4 @@
|
||||
<field name="view_mode">tree</field>
|
||||
</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>
|
||||
85
sf_demand_plan/views/demand_plan_info.xml
Normal file
85
sf_demand_plan/views/demand_plan_info.xml
Normal 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>
|
||||
18
sf_demand_plan/views/menu_view.xml
Normal file
18
sf_demand_plan/views/menu_view.xml
Normal 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>
|
||||
29
sf_demand_plan/views/sale_order_views.xml
Normal file
29
sf_demand_plan/views/sale_order_views.xml
Normal 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>
|
||||
@@ -1 +1,2 @@
|
||||
from . import sf_demand_plan_print_wizard
|
||||
from . import sf_release_plan_wizard
|
||||
|
||||
20
sf_demand_plan/wizard/sf_release_plan_wizard.py
Normal file
20
sf_demand_plan/wizard/sf_release_plan_wizard.py
Normal 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()
|
||||
22
sf_demand_plan/wizard/sf_release_plan_wizard_views.xml
Normal file
22
sf_demand_plan/wizard/sf_release_plan_wizard_views.xml
Normal 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>
|
||||
@@ -44,7 +44,8 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
|
||||
if productions:
|
||||
# 修改需求计划中的程序工时
|
||||
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']:
|
||||
demand_plan.write(
|
||||
{'processing_time': ret['total_estimated_time']})
|
||||
|
||||
Reference in New Issue
Block a user