diff --git a/jikimo_purchase_request/models/sale_order.py b/jikimo_purchase_request/models/sale_order.py
index 3dc88287..78f33133 100644
--- a/jikimo_purchase_request/models/sale_order.py
+++ b/jikimo_purchase_request/models/sale_order.py
@@ -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
diff --git a/sf_demand_plan/__manifest__.py b/sf_demand_plan/__manifest__.py
index 59457102..474c1608 100644
--- a/sf_demand_plan/__manifest__.py
+++ b/sf_demand_plan/__manifest__.py
@@ -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': [
],
diff --git a/sf_demand_plan/migrations/1.1/post-migrate.py b/sf_demand_plan/migrations/1.1/post-migrate.py
new file mode 100644
index 00000000..5f2a0286
--- /dev/null
+++ b/sf_demand_plan/migrations/1.1/post-migrate.py
@@ -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})
diff --git a/sf_demand_plan/models/__init__.py b/sf_demand_plan/models/__init__.py
index 70c1bbc8..9913da30 100644
--- a/sf_demand_plan/models/__init__.py
+++ b/sf_demand_plan/models/__init__.py
@@ -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
diff --git a/sf_demand_plan/models/mrp_bom.py b/sf_demand_plan/models/mrp_bom.py
new file mode 100644
index 00000000..8973cb87
--- /dev/null
+++ b/sf_demand_plan/models/mrp_bom.py
@@ -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
diff --git a/sf_demand_plan/models/mrp_production.py b/sf_demand_plan/models/mrp_production.py
new file mode 100644
index 00000000..476bcbaa
--- /dev/null
+++ b/sf_demand_plan/models/mrp_production.py
@@ -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)
diff --git a/sf_demand_plan/models/purchase_order.py b/sf_demand_plan/models/purchase_order.py
new file mode 100644
index 00000000..5bc07313
--- /dev/null
+++ b/sf_demand_plan/models/purchase_order.py
@@ -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
diff --git a/sf_demand_plan/models/purchase_request.py b/sf_demand_plan/models/purchase_request.py
new file mode 100644
index 00000000..9cb15518
--- /dev/null
+++ b/sf_demand_plan/models/purchase_request.py
@@ -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",
+ }
diff --git a/sf_demand_plan/models/sale_order.py b/sf_demand_plan/models/sale_order.py
index 27e5c603..fe8966a6 100644
--- a/sf_demand_plan/models/sale_order.py
+++ b/sf_demand_plan/models/sale_order.py
@@ -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',
+ }
diff --git a/sf_demand_plan/models/sf_demand_plan.py b/sf_demand_plan/models/sf_demand_plan.py
new file mode 100644
index 00000000..9c55aadd
--- /dev/null
+++ b/sf_demand_plan/models/sf_demand_plan.py
@@ -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()
diff --git a/sf_demand_plan/models/sf_production_demand_plan.py b/sf_demand_plan/models/sf_production_demand_plan.py
index d312a2f5..f0bbd708 100644
--- a/sf_demand_plan/models/sf_production_demand_plan.py
+++ b/sf_demand_plan/models/sf_production_demand_plan.py
@@ -6,23 +6,18 @@ from odoo.exceptions import ValidationError
from odoo.tools import float_compare
from datetime import datetime, timedelta
from odoo.exceptions import UserError
+import re
class SfProductionDemandPlan(models.Model):
_name = 'sf.production.demand.plan'
_description = 'sf_production_demand_plan'
- def _get_machining_precision(self):
- machinings = self.env['sf.machining.accuracy'].sudo().search([])
- list = [(m.sync_id, m.name) for m in machinings]
- return list
+ def get_location_id(self):
+ stock_location = self.env['stock.location'].sudo().search([('name', '=', '客户')], limit=1)
+ return stock_location.id
- priority = fields.Selection([
- ('1', '紧急'),
- ('2', '高'),
- ('3', '中'),
- ('4', '低'),
- ], string='优先级', default='3')
+ priority = fields.Selection(related='demand_plan_id.priority', string='优先级')
status = fields.Selection([
('10', '草稿'),
('20', '待确认'),
@@ -30,10 +25,12 @@ class SfProductionDemandPlan(models.Model):
('50', '待下达生产'),
('60', '已下达'),
('100', '取消'),
- ], string='状态', compute='_compute_status', store=True)
- sale_order_id = fields.Many2one(comodel_name="sale.order",
+ ], string='状态', default='30', readonly=True)
+ demand_plan_id = fields.Many2one(comodel_name="sf.demand.plan",
+ string="物料需求", readonly=True)
+ sale_order_id = fields.Many2one(comodel_name="sale.order", related='demand_plan_id.sale_order_id',
string="销售订单", readonly=True)
- sale_order_line_id = fields.Many2one(comodel_name="sale.order.line",
+ sale_order_line_id = fields.Many2one(comodel_name="sale.order.line", related='demand_plan_id.sale_order_line_id',
string="销售订单明细", readonly=True)
sale_order_line_number = fields.Char(string='销售订单行', compute='_compute_sale_order_line_number', store=True)
company_id = fields.Many2one(
@@ -49,47 +46,54 @@ class SfProductionDemandPlan(models.Model):
string='产品', store=True, index=True)
model_id = fields.Char('模型ID', related='product_id.model_id')
part_name = fields.Char('零件名称', related='product_id.part_name')
- part_number = fields.Char('零件图号', compute='_compute_part_number', store=True)
+ part_number = fields.Char('零件图号', related='demand_plan_id.part_number')
is_incoming_material = fields.Boolean('客供料', related='sale_order_line_id.is_incoming_material', store=True)
+
+ new_supply_method = fields.Selection([
+ ('custom_made', "自制"),
+ ('purchase', "外购"),
+ ('outsourcing', "委外加工"),
+ ], string='供货方式', required=True)
+
+ custom_made_type = fields.Selection([
+ ('automation', "自动化产线加工"),
+ ('manual', "人工线下加工"),
+ ], string='自制类型', compute='_compute_custom_made_type', store=True)
+
supply_method = fields.Selection([
('automation', "自动化产线加工"),
('manual', "人工线下加工"),
('purchase', "外购"),
('outsourcing', "委外加工"),
- ], string='供货方式', related='sale_order_line_id.supply_method', store=True)
+ ], string='供货方式', compute='_compute_supply_method', store=True, readonly=True)
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)
inventory_quantity_auto_apply = fields.Float(
string="成品库存",
- compute='_compute_inventory_quantity_auto_apply'
+ related='demand_plan_id.inventory_quantity_auto_apply'
)
qty_delivered = fields.Float(
"交货数量", related='sale_order_line_id.qty_delivered')
qty_to_deliver = fields.Float(
- "待交货数量", related='sale_order_line_id.qty_to_deliver')
+ "待交货数量", compute='_compute_qty_to_deliver', store=True)
model_long = fields.Char('尺寸(mm)', compute='_compute_model_long')
blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类',
related='product_id.blank_type')
- blank_precision = fields.Selection([('精坯', '精坯'), ('粗坯', '粗坯')], string='坯料类型', related='product_id.blank_precision')
- embryo_long = fields.Char('坯料尺寸(mm)', compute='_compute_embryo_long')
- materials_id = fields.Char('材料', compute='_compute_materials_id', store=True)
- model_machining_precision = fields.Selection(selection=_get_machining_precision, string='精度',
- related='product_id.model_machining_precision')
- model_process_parameters_ids = fields.Many2many('sf.production.process.parameter',
- 'plan_process_parameter_rel',
- string='表面工艺',
- compute='_compute_model_process_parameters_ids'
- , store=True
- )
+ blank_precision = fields.Selection([('精坯', '精坯'), ('粗坯', '粗坯')], string='坯料类型',
+ related='product_id.blank_precision')
+ embryo_long = fields.Char('坯料尺寸(mm)', related='demand_plan_id.embryo_long')
+ materials_id = fields.Char('材料', related='demand_plan_id.materials_id')
+ model_machining_precision = fields.Selection(related='product_id.model_machining_precision', string='精度')
+ model_process_parameters_ids = fields.Many2many(related='demand_plan_id.model_process_parameters_ids',
+ string='表面工艺', )
product_remark = fields.Char("产品备注", related='product_id.model_remark')
order_code = fields.Char('E-SHOP订单号', related='sale_order_id.order_code')
order_state = fields.Selection(
string='订单状态',
related='sale_order_line_id.state')
- route_id = fields.Many2one('stock.route', string='路线', related='sale_order_line_id.route_id', store=True)
- route_ids = fields.Many2many('stock.route', 'stock_route_demand_plan', 'demand_plan_id', 'route_id', '路线',
+ route_ids = fields.Many2many('stock.route', 'stock_route_demand_plan', 'demand_plan_id', 'route_id', '库存路线',
domain=[('demand_plan_selectable', '=', True)], compute='_compute_route_ids',
store=True)
contract_date = fields.Date('合同日期', related='sale_order_id.contract_date')
@@ -107,37 +111,115 @@ class SfProductionDemandPlan(models.Model):
print_count = fields.Char('打印次数', default='T0C0', readonly=True)
sequence = fields.Integer('序号')
- hide_action_open_mrp_production = fields.Boolean(
- string='显示待工艺确认按钮',
- compute='_compute_hid_button',
+ mrp_production_ids = fields.Many2many(
+ 'mrp.production',
+ compute='_compute_mrp_production_ids',
+ string='与此相需求计划关联的制造订单',
+ store=True)
+
+ hide_release_production_order = fields.Boolean(
+ string='显示下发生产按钮',
+ compute='_compute_hide_release_production_order',
default=False
)
- hide_action_purchase_orders = fields.Boolean(
- string='显示采购按钮',
- compute='_compute_hide_action_purchase_orders',
- default=False
+ readonly_custom_made_type = fields.Boolean(
+ related='demand_plan_id.readonly_custom_made_type',
+ string='字段自制类型只读'
)
- hide_action_stock_picking = fields.Boolean(
- string='显示调拨单按钮',
- compute='_compute_hide_action_stock_picking',
- default=False
- )
+ # hide_action_open_mrp_production = fields.Boolean(
+ # string='显示待工艺确认按钮',
+ # compute='_compute_hid_button',
+ # default=False
+ # )
+ #
+ # hide_action_purchase_orders = fields.Boolean(
+ # string='显示采购按钮',
+ # compute='_compute_hide_action_purchase_orders',
+ # default=False
+ # )
+ #
+ # hide_action_stock_picking = fields.Boolean(
+ # string='显示调拨单按钮',
+ # compute='_compute_hide_action_stock_picking',
+ # default=False
+ # )
+ #
+ # hide_action_outsourcing_stock_picking = fields.Boolean(
+ # string='委外显示调拨单按钮',
+ # compute='_compute_hide_action_stock_picking',
+ # default=False
+ # )
+ #
+ # hide_action_view_programming = fields.Boolean(
+ # string='显示编程单按钮',
+ # compute='_compute_hid_button',
+ # default=False
+ # )
+ #
+ # outsourcing_purchase_request = fields.Char('委外采购申请单')
- hide_action_outsourcing_stock_picking = fields.Boolean(
- string='委外显示调拨单按钮',
- compute='_compute_hide_action_stock_picking',
- default=False
- )
+ plan_uom_qty = fields.Float(string="计划量", required=True)
+ procurement_reason = fields.Selection([
+ ('销售订单', "销售订单"),
+ ('需求预测', "需求预测"),
+ ('生产报废', "生产报废"),
+ ], string='补货原因', default='销售订单', readonly=True)
- hide_action_view_programming = fields.Boolean(
- string='显示编程单按钮',
- compute='_compute_hid_button',
- default=False
- )
+ blank_arrival_date = fields.Date('采购计划到货(坯料)')
+ finished_product_arrival_date = fields.Date('采购计划到货(成品)')
+ bom_id = fields.Many2one('mrp.bom', string="BOM", readonly=True)
+ location_id = fields.Many2one('stock.location', string='需求位置', default=get_location_id, readonly=True)
- outsourcing_purchase_request = fields.Char('委外采购申请单')
+ @api.constrains('plan_uom_qty')
+ def _check_plan_uom_qty(self):
+ line_ids = self.filtered(lambda p: p.plan_uom_qty == 0 or p.plan_uom_qty < 0)
+ if line_ids:
+ raise ValidationError(_("计划量不能小于等于0"))
+
+ @api.constrains('new_supply_method')
+ def _check_new_supply_method(self):
+ product_name = []
+ for line in self:
+ if line.new_supply_method == 'purchase' and line.is_incoming_material:
+ product_name.append(line.product_id.display_name)
+ if product_name:
+ unique_product_names = list(set(product_name))
+ raise UserError('当前(%s)产品为客供料,不能选择外购' % ','.join(unique_product_names))
+
+ @api.depends('new_supply_method')
+ def _compute_custom_made_type(self):
+ DemandPlan = self.env['sf.production.demand.plan'].sudo()
+ for line in self:
+ if line.new_supply_method != "custom_made":
+ line.custom_made_type = False
+ else:
+ demand_plan_id = line.demand_plan_id._origin.id
+ demand_plan = DemandPlan.search([
+ ('demand_plan_id', '=', demand_plan_id),
+ ('new_supply_method', '=', 'custom_made'),
+ ('status', 'in', ('50', '60'))
+ ], limit=1)
+ if demand_plan:
+ line.custom_made_type = demand_plan.custom_made_type
+
+ @api.depends('new_supply_method', 'custom_made_type')
+ def _compute_supply_method(self):
+ for line in self:
+ if line.new_supply_method == 'custom_made':
+ line.supply_method = line.custom_made_type
+ else:
+ line.supply_method = line.new_supply_method
+
+ @api.depends('sale_order_line_id.qty_to_deliver')
+ def _compute_qty_to_deliver(self):
+ for line in self:
+ if float_compare(line.sale_order_line_id.qty_to_deliver, 0,
+ precision_rounding=line.product_id.uom_id.rounding) == -1:
+ line.qty_to_deliver = 0
+ else:
+ line.qty_to_deliver = line.sale_order_line_id.qty_to_deliver
@api.depends('supply_method')
def _compute_route_ids(self):
@@ -148,42 +230,9 @@ class SfProductionDemandPlan(models.Model):
[('demand_plan_selectable', '=', True), ('stock_route_group_ids', '=', group_id.id)])
if route_ids:
pdp.route_ids = route_ids.ids
- break
+ continue
pdp.route_ids = None
- @api.depends('sale_order_id.state', 'sale_order_id.mrp_production_ids.schedule_state', 'sale_order_id.order_line',
- 'sale_order_id.mrp_production_ids.state')
- def _compute_status(self):
- for record in self:
- if record.sale_order_id:
- sale_order_state = record.sale_order_id.state
- if sale_order_state in ('draft', 'sent', 'supply method'):
- record.status = '20' # 待确认
- if record.supply_method in ('purchase', 'outsourcing') and sale_order_state in (
- 'sale', 'processing', 'physical_distribution', 'delivered',
- 'done') and sale_order_state != 'cancel':
- record.status = '60' # 已下达
- if record.supply_method in ('automation', 'manual'):
- if sale_order_state in (
- 'sale', 'processing', 'physical_distribution', 'delivered',
- 'done') and sale_order_state != 'cancel':
- record.status = '30' # 需求确认
- # 检查所有制造订单的排程单状态,有一个为待排程状态,就为待下达生产
- pending_productions = record.sale_order_id.mrp_production_ids.filtered(
- lambda p: p.state == 'confirmed' and p.product_id.id == record.product_id.id
- )
- if pending_productions:
- record.status = '50' # 待下达生产
- # 检查所有制造订单的排程单状态
- if record.sale_order_id.mrp_production_ids:
- product_productions = record.sale_order_id.mrp_production_ids.filtered(
- lambda p: p.product_id.id == record.product_id.id
- )
- if product_productions and all(order.schedule_state != '未排' for order in product_productions):
- record.status = '60' # 已下达
- if sale_order_state == 'cancel' or not record.sale_order_line_id:
- record.status = '100' # 取消
-
@api.depends('sale_order_line_id.product_id.name')
def _compute_sale_order_line_number(self):
for line in self:
@@ -192,18 +241,6 @@ class SfProductionDemandPlan(models.Model):
else:
line.sale_order_line_number = None
- @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.length', 'product_id.width', 'product_id.height')
def _compute_model_long(self):
for line in self:
@@ -212,81 +249,26 @@ class SfProductionDemandPlan(models.Model):
else:
line.model_long = None
- @api.depends('product_id.model_long', 'product_id.model_width', 'product_id.model_height', 'product_id.blank_type')
- def _compute_embryo_long(self):
- for line in self:
- if line.product_id:
- if line.product_id.blank_type == '圆料':
- line.embryo_long = f"Ø{round(line.product_id.model_width, 3)}*{round(line.product_id.model_long, 3)}"
- else:
- 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.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_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('sale_order_id.mrp_production_ids.workorder_ids.date_start')
+ @api.depends('mrp_production_ids.workorder_ids.date_start')
def _compute_actual_start_date(self):
for record in self:
- if record.sale_order_id and record.sale_order_id.mrp_production_ids:
- manufacturing_orders = record.sale_order_id.mrp_production_ids.filtered(
- lambda mo: mo.product_id == record.product_id)
- if manufacturing_orders:
- start_dates = [
- workorder.date_start for mo in manufacturing_orders
- for workorder in mo.workorder_ids if workorder.date_start
- ]
- record.actual_start_date = min(start_dates) if start_dates else None
- else:
- record.actual_start_date = None
+ if record.mrp_production_ids:
+ start_dates = [
+ workorder.date_start for mo in record.mrp_production_ids
+ for workorder in mo.workorder_ids if workorder.date_start
+ ]
+ record.actual_start_date = min(start_dates) if start_dates else None
else:
record.actual_start_date = None
- @api.depends('sale_order_id.mrp_production_ids.workorder_ids.state',
- 'sale_order_id.mrp_production_ids.workorder_ids.date_finished')
+ @api.depends('mrp_production_ids.workorder_ids.state',
+ 'mrp_production_ids.workorder_ids.date_finished')
def _compute_actual_end_date(self):
for record in self:
- if record.sale_order_id and record.sale_order_id.mrp_production_ids:
- manufacturing_orders = record.sale_order_id.mrp_production_ids.filtered(
- lambda mo: mo.product_id == record.product_id)
- finished_orders = manufacturing_orders.filtered(lambda mo: mo.state == 'done')
+ if record.mrp_production_ids:
+ finished_orders = record.mrp_production_ids.filtered(lambda mo: mo.state == 'done')
sum_product_qty = sum(finished_orders.mapped('product_qty'))
- if finished_orders and float_compare(sum_product_qty, record.product_uom_qty,
+ if finished_orders and float_compare(sum_product_qty, record.plan_uom_qty,
precision_rounding=record.product_id.uom_id.rounding) >= 0:
end_dates = [
workorder.date_finished for mo in finished_orders
@@ -298,41 +280,49 @@ class SfProductionDemandPlan(models.Model):
else:
record.actual_end_date = None
- @api.depends('sale_order_id.mrp_production_ids.move_raw_ids.reserved_availability')
+ @api.depends('mrp_production_ids.move_raw_ids.reserved_availability')
def _compute_material_check(self):
for record in self:
- if record.sale_order_id and record.sale_order_id.mrp_production_ids:
- manufacturing_orders = record.sale_order_id.mrp_production_ids.filtered(
- lambda mo: mo.product_id == record.product_id)
-
- if manufacturing_orders and manufacturing_orders.move_raw_ids:
- # 获取完成的制造订单
- done_manufacturing = manufacturing_orders.filtered(lambda mo: mo.state == 'done')
- product_qty = sum(done_manufacturing.mapped('product_qty'))
- # 需求数量-完成数量
- product_uom_qty = record.product_uom_qty - product_qty
- total_reserved_availability = sum(manufacturing_orders.mapped('move_raw_ids.reserved_availability'))
- if float_compare(total_reserved_availability, product_uom_qty,
- precision_rounding=record.product_id.uom_id.rounding) >= 0:
- record.material_check = '1' # 已齐套
- else:
- record.material_check = '0' # 未齐套
+ if record.mrp_production_ids and record.mrp_production_ids.move_raw_ids:
+ total_reserved_availability = sum(
+ record.mrp_production_ids.mapped('move_raw_ids.reserved_availability'))
+ if float_compare(total_reserved_availability, record.plan_uom_qty,
+ precision_rounding=record.product_id.uom_id.rounding) >= 0:
+ record.material_check = '1' # 已齐套
else:
- record.material_check = None
+ record.material_check = '0' # 未齐套
else:
record.material_check = None
+ @api.depends('status')
+ def _compute_mrp_production_ids(self):
+ for record in self:
+ if record.status in ('50', '60'):
+ record.mrp_production_ids = self.env['mrp.production'].sudo().search(
+ [('demand_plan_line_id', '=', record.id)]).ids
+ else:
+ record.mrp_production_ids = None
+
+ @api.depends('mrp_production_ids.state')
+ def _compute_hide_release_production_order(self):
+ for record in self:
+ # 检查这条需求计划所有制造订单的排程单状态,有一个为待排程状态,就显示改按钮
+ record.hide_release_production_order = bool(record.mrp_production_ids.filtered(
+ lambda p: p.state == 'confirmed'
+ ))
+
@api.constrains('planned_start_date')
def _check_planned_start_date(self):
for record in self:
if record.planned_start_date and record.planned_start_date < fields.Date.today():
raise ValidationError("计划开工日期必须大于或等于今天。")
- def release_production_order(self):
+ def button_release_production(self):
+ self.ensure_one()
if not self.planned_start_date:
raise ValidationError("请先填写计划开工日期")
- pro_plan_list = self.env['sf.production.plan'].search(
- [('product_id', '=', self.product_id.id), ('state', '=', 'draft')])
+ pro_plan_list = self.env['sf.production.plan'].sudo().search(
+ [('production_id', 'in', self.mrp_production_ids.ids), ('state', '=', 'draft')])
sf_production_line = self.env['sf.production.line'].sudo().search(
[('name', '=', '1#CNC自动生产线')], limit=1)
if sf_production_line:
@@ -343,11 +333,34 @@ class SfProductionDemandPlan(models.Model):
pro_plan_list.production_line_id = sf_production_line.id
pro_plan_list.date_planned_start = date_planned_start
self._do_production_schedule(pro_plan_list)
+ self.status = '60'
+ self.update_sale_order_state()
def _do_production_schedule(self, pro_plan_list):
for pro_plan in pro_plan_list:
pro_plan.do_production_schedule()
+ def update_sale_order_state(self):
+ demand_plan = self.env['sf.demand.plan'].sudo().search([('sale_order_id', '=', self.sale_order_id.id)])
+ demand_plan_state = demand_plan.filtered(lambda line: line.state != '40')
+ if not demand_plan_state:
+ # 修改销售订单为加工中
+ self.sale_order_id.state = 'processing'
+
+ def edit_button(self):
+ self.ensure_one()
+ action = {
+ 'name': _("需求计划"),
+ 'type': 'ir.actions.act_window',
+ 'view_mode': 'form',
+ 'views': [[self.env.ref('sf_demand_plan.view_sf_demand_plan_form').id, 'form']],
+ 'res_model': 'sf.demand.plan',
+ 'res_id': self.demand_plan_id.id,
+ }
+ if self.demand_plan_id.state == '40':
+ action.update({'flags': {'mode': 'readonly'}})
+ return action
+
def button_action_print(self):
return {
'res_model': 'sf.demand.plan.print.wizard',
@@ -358,211 +371,499 @@ class SfProductionDemandPlan(models.Model):
'target': 'new',
}
- @api.depends('sale_order_id.mrp_production_ids.state', 'sale_order_id.mrp_production_ids.programming_state')
- def _compute_hid_button(self):
- for record in self:
- mrp_production_ids = record.sale_order_id.mrp_production_ids.filtered(
- lambda p: p.state == 'technology_to_confirmed' and p.product_id.id == record.product_id.id
- )
- record.hide_action_open_mrp_production = bool(mrp_production_ids) and record.supply_method in (
- 'automation', 'manual')
- programming_mrp_production_ids = record.sale_order_id.mrp_production_ids.filtered(
- lambda p: p.programming_state == '编程中' and p.product_id.id == record.product_id.id
- )
- record.hide_action_view_programming = bool(programming_mrp_production_ids)
+ # @api.depends('sale_order_id.mrp_production_ids.state', 'sale_order_id.mrp_production_ids.programming_state')
+ # def _compute_hid_button(self):
+ # for record in self:
+ # mrp_production_ids = record.sale_order_id.mrp_production_ids.filtered(
+ # lambda p: p.state == 'technology_to_confirmed' and p.product_id.id == record.product_id.id
+ # )
+ # record.hide_action_open_mrp_production = bool(mrp_production_ids) and record.supply_method in (
+ # 'automation', 'manual')
+ # programming_mrp_production_ids = record.sale_order_id.mrp_production_ids.filtered(
+ # lambda p: p.programming_state == '编程中' and p.product_id.id == record.product_id.id
+ # )
+ # record.hide_action_view_programming = bool(programming_mrp_production_ids)
+ #
+ # def _compute_hide_action_purchase_orders(self):
+ # for record in self:
+ # record.hide_action_purchase_orders = False
+ # outsourcing_purchase_request = []
+ # if record.supply_method in ('automation',
+ # 'manual') and record.material_check == '0' and not record.sale_order_line_id.is_incoming_material:
+ # mrp_production = record.sale_order_id.mrp_production_ids.filtered(
+ # lambda p: p.product_id.id == record.product_id.id
+ # ).sorted(key=lambda p: p.id)
+ # if mrp_production:
+ # raw_materials = mrp_production.mapped('move_raw_ids.product_id')
+ # if raw_materials:
+ # purchase_orders = self.env['purchase.order'].sudo().search([
+ # ('state', '=', 'purchase'),
+ # ('order_line.product_id', 'in', raw_materials.ids)
+ # ])
+ # total_purchase_quantity = sum(
+ # sum(
+ # order.order_line.filtered(
+ # lambda line: line.product_id in raw_materials
+ # ).mapped('product_qty')
+ # )
+ # for order in purchase_orders
+ # )
+ # if float_compare(total_purchase_quantity, record.product_uom_qty,
+ # precision_rounding=record.product_id.uom_id.rounding) == -1:
+ # pr_ids = self.env['purchase.request'].sudo().search(
+ # [('line_ids.product_id', 'in', raw_materials.ids), ('state', '!=', 'done')])
+ # outsourcing_purchase_request.extend(pr_ids.ids)
+ # elif record.supply_method in ('purchase', 'outsourcing'):
+ # purchase_orders = self.env['purchase.order'].sudo().search([
+ # ('state', 'in', ('purchase', 'done')),
+ # ('order_line.product_id', '=', record.product_id.id)
+ # ])
+ # total_purchase_quantity = sum(
+ # sum(
+ # order.order_line.filtered(
+ # lambda line: line.product_id in record.product_id
+ # ).mapped('product_qty')
+ # )
+ # for order in purchase_orders
+ # )
+ #
+ # if float_compare(total_purchase_quantity, record.product_uom_qty,
+ # precision_rounding=record.product_id.uom_id.rounding) == -1:
+ # pr_ids = self.env['purchase.request'].sudo().search(
+ # [('origin', 'like', record.sale_order_id.name), ('state', '!=', 'done')])
+ # outsourcing_purchase_request.extend(pr_ids.ids)
+ # if record.supply_method == 'outsourcing' and not record.sale_order_line_id.is_incoming_material:
+ # bom_line_ids = record.product_id.bom_ids.bom_line_ids
+ # if bom_line_ids:
+ # # BOM_数量
+ # total_product_qty = sum(line.product_qty for line in bom_line_ids)
+ # bom_product_ids = bom_line_ids.mapped('product_id')
+ # product_purchase_orders = self.env['purchase.order'].sudo().search([
+ # ('state', 'in', ('purchase', 'done')),
+ # ('order_line.product_id', 'in', bom_product_ids.ids)
+ # ])
+ # # 购订单_数量
+ # total_outsourcing_purchase_quantity = sum(
+ # sum(
+ # order.order_line.filtered(
+ # lambda line: line.product_id in bom_product_ids
+ # ).mapped('product_qty')
+ # )
+ # for order in product_purchase_orders
+ # )
+ # quantity = total_outsourcing_purchase_quantity / total_product_qty
+ # if float_compare(quantity, record.product_uom_qty,
+ # precision_rounding=record.product_id.uom_id.rounding) == -1:
+ # purchase_request = self.env['purchase.request'].sudo().search(
+ # [('line_ids.product_id', 'in', bom_product_ids.ids),
+ # ('line_ids.purchase_state', 'not in', ('purchase', 'done')), ('state', '!=', 'done')])
+ # outsourcing_purchase_request.extend(purchase_request.ids)
+ # record.outsourcing_purchase_request = json.dumps(outsourcing_purchase_request)
+ # if outsourcing_purchase_request:
+ # record.hide_action_purchase_orders = True
+ #
+ # @api.depends('sale_order_id.mrp_production_ids.picking_ids.state', 'sale_order_id.picking_ids.state')
+ # def _compute_hide_action_stock_picking(self):
+ # for record in self:
+ # record.hide_action_stock_picking = False
+ # record.hide_action_outsourcing_stock_picking = False
+ # if record.supply_method in ('automation', 'manual'):
+ # manufacturing_orders = record.sale_order_id.mrp_production_ids.filtered(
+ # lambda p: p.product_id.id == record.product_id.id
+ # )
+ # record.hide_action_stock_picking = bool(manufacturing_orders.mapped('picking_ids').filtered(
+ # lambda p: p.state == 'assigned'))
+ # elif record.supply_method in ('purchase', 'outsourcing'):
+ # assigned_picking_ids = record.sale_order_id.picking_ids.filtered(
+ # lambda
+ # p: p.state == 'assigned' and p.picking_type_id.name != '发料出库' and p.move_line_ids.product_id in record.product_id)
+ # if record.supply_method == 'outsourcing':
+ # outsourcing_assigned_picking_ids = record.get_outsourcing_picking_ids()
+ # record.hide_action_outsourcing_stock_picking = outsourcing_assigned_picking_ids
+ # record.hide_action_stock_picking = assigned_picking_ids or outsourcing_assigned_picking_ids
+ # else:
+ # record.hide_action_stock_picking = assigned_picking_ids
+ #
+ # def get_outsourcing_picking_ids(self):
+ # order_ids = self.env['purchase.order'].sudo().search(
+ # [('order_line.product_id', 'in', self.product_id.ids),
+ # ('purchase_type', '=', 'outsourcing')])
+ # outsourcing_picking_ids = order_ids._get_subcontracting_resupplies()
+ # outsourcing_assigned_picking_ids = outsourcing_picking_ids.filtered(lambda p: p.state == 'assigned')
+ # return outsourcing_assigned_picking_ids
- def _compute_hide_action_purchase_orders(self):
- for record in self:
- record.hide_action_purchase_orders = False
- outsourcing_purchase_request = []
- if record.supply_method in ('automation',
- 'manual') and record.material_check == '0' and not record.sale_order_line_id.is_incoming_material:
- mrp_production = record.sale_order_id.mrp_production_ids.filtered(
- lambda p: p.product_id.id == record.product_id.id
- ).sorted(key=lambda p: p.id)
- if mrp_production:
- raw_materials = mrp_production.mapped('move_raw_ids.product_id')
- if raw_materials:
- purchase_orders = self.env['purchase.order'].sudo().search([
- ('state', '=', 'purchase'),
- ('order_line.product_id', 'in', raw_materials.ids)
- ])
- total_purchase_quantity = sum(
- sum(
- order.order_line.filtered(
- lambda line: line.product_id in raw_materials
- ).mapped('product_qty')
- )
- for order in purchase_orders
- )
- if float_compare(total_purchase_quantity, record.product_uom_qty,
- precision_rounding=record.product_id.uom_id.rounding) == -1:
- pr_ids = self.env['purchase.request'].sudo().search(
- [('line_ids.product_id', 'in', raw_materials.ids), ('state', '!=', 'done')])
- outsourcing_purchase_request.extend(pr_ids.ids)
- elif record.supply_method in ('purchase', 'outsourcing'):
- purchase_orders = self.env['purchase.order'].sudo().search([
- ('state', 'in', ('purchase', 'done')),
- ('order_line.product_id', '=', record.product_id.id)
- ])
- total_purchase_quantity = sum(
- sum(
- order.order_line.filtered(
- lambda line: line.product_id in record.product_id
- ).mapped('product_qty')
- )
- for order in purchase_orders
- )
+ # def action_open_sale_order(self):
+ # self.ensure_one()
+ # return {
+ # 'type': 'ir.actions.act_window',
+ # 'res_model': 'sale.order',
+ # 'res_id': self.sale_order_id.id,
+ # 'view_mode': 'form',
+ # }
- if float_compare(total_purchase_quantity, record.product_uom_qty,
- precision_rounding=record.product_id.uom_id.rounding) == -1:
- pr_ids = self.env['purchase.request'].sudo().search(
- [('origin', 'like', record.sale_order_id.name), ('state', '!=', 'done')])
- outsourcing_purchase_request.extend(pr_ids.ids)
- if record.supply_method == 'outsourcing' and not record.sale_order_line_id.is_incoming_material:
- bom_line_ids = record.product_id.bom_ids.bom_line_ids
- if bom_line_ids:
- # BOM_数量
- total_product_qty = sum(line.product_qty for line in bom_line_ids)
- bom_product_ids = bom_line_ids.mapped('product_id')
- product_purchase_orders = self.env['purchase.order'].sudo().search([
- ('state', 'in', ('purchase', 'done')),
- ('order_line.product_id', 'in', bom_product_ids.ids)
- ])
- # 购订单_数量
- total_outsourcing_purchase_quantity = sum(
- sum(
- order.order_line.filtered(
- lambda line: line.product_id in bom_product_ids
- ).mapped('product_qty')
- )
- for order in product_purchase_orders
- )
- quantity = total_outsourcing_purchase_quantity / total_product_qty
- if float_compare(quantity, record.product_uom_qty,
- precision_rounding=record.product_id.uom_id.rounding) == -1:
- purchase_request = self.env['purchase.request'].sudo().search(
- [('line_ids.product_id', 'in', bom_product_ids.ids),
- ('line_ids.purchase_state', 'not in', ('purchase', 'done')), ('state', '!=', 'done')])
- outsourcing_purchase_request.extend(purchase_request.ids)
- record.outsourcing_purchase_request = json.dumps(outsourcing_purchase_request)
- if outsourcing_purchase_request:
- record.hide_action_purchase_orders = True
+ # def action_open_mrp_production(self):
+ # self.ensure_one()
+ # mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered(
+ # lambda p: p.state == 'technology_to_confirmed' and p.product_id.id == self.product_id.id
+ # )
+ # action = {
+ # 'res_model': 'mrp.production',
+ # 'type': 'ir.actions.act_window',
+ # }
+ # if len(mrp_production_ids) == 1:
+ # action.update({
+ # 'view_mode': 'form',
+ # 'res_id': mrp_production_ids.id,
+ # })
+ # else:
+ # action.update({
+ # 'name': _("制造订单列表"),
+ # 'domain': [('id', 'in', mrp_production_ids.ids)],
+ # 'view_mode': 'tree,form',
+ # })
+ # return action
- @api.depends('sale_order_id.mrp_production_ids.picking_ids.state', 'sale_order_id.picking_ids.state')
- def _compute_hide_action_stock_picking(self):
- for record in self:
- record.hide_action_stock_picking = False
- record.hide_action_outsourcing_stock_picking = False
- if record.supply_method in ('automation', 'manual'):
- manufacturing_orders = record.sale_order_id.mrp_production_ids.filtered(
- lambda p: p.product_id.id == record.product_id.id
- )
- record.hide_action_stock_picking = bool(manufacturing_orders.mapped('picking_ids').filtered(
- lambda p: p.state == 'assigned'))
- elif record.supply_method in ('purchase', 'outsourcing'):
- assigned_picking_ids = record.sale_order_id.picking_ids.filtered(
- lambda
- p: p.state == 'assigned' and p.picking_type_id.name != '发料出库' and p.move_line_ids.product_id in record.product_id)
- if record.supply_method == 'outsourcing':
- outsourcing_assigned_picking_ids = record.get_outsourcing_picking_ids()
- record.hide_action_outsourcing_stock_picking = outsourcing_assigned_picking_ids
- record.hide_action_stock_picking = assigned_picking_ids or outsourcing_assigned_picking_ids
- else:
- record.hide_action_stock_picking = assigned_picking_ids
+ # def action_view_purchase_request(self):
+ # self.ensure_one()
+ # pr_ids = self.env['purchase.request'].sudo().search(
+ # [('id', 'in', ast.literal_eval(self.outsourcing_purchase_request))])
+ # action = {
+ # 'res_model': 'purchase.request',
+ # 'type': 'ir.actions.act_window',
+ # }
+ # if len(pr_ids) == 1:
+ # action.update({
+ # 'view_mode': 'form',
+ # 'res_id': pr_ids[0].id,
+ # })
+ # else:
+ # action.update({
+ # 'name': _("采购申请"),
+ # 'domain': [('id', 'in', pr_ids.ids)],
+ # 'view_mode': 'tree,form',
+ # })
+ # return action
- def get_outsourcing_picking_ids(self):
- order_ids = self.env['purchase.order'].sudo().search(
- [('order_line.product_id', 'in', self.product_id.ids),
- ('purchase_type', '=', 'outsourcing')])
- outsourcing_picking_ids = order_ids._get_subcontracting_resupplies()
- outsourcing_assigned_picking_ids = outsourcing_picking_ids.filtered(lambda p: p.state == 'assigned')
- return outsourcing_assigned_picking_ids
+ # def action_view_stock_picking(self):
+ # self.ensure_one()
+ # action = self.env["ir.actions.actions"]._for_xml_id("stock.action_picking_tree_all")
+ # picking_ids = None
+ # if self.supply_method in ('automation', 'manual'):
+ # mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered(
+ # lambda p: p.product_id.id == self.product_id.id
+ # )
+ # picking_ids = mrp_production_ids.mapped('picking_ids').filtered(
+ # lambda p: p.state == 'assigned')
+ # elif self.supply_method in ('purchase', 'outsourcing'):
+ # picking_ids = self.sale_order_id.picking_ids.filtered(
+ # lambda
+ # p: p.state == 'assigned' and p.picking_type_id.name != '发料出库' and p.move_line_ids.product_id in self.product_id)
+ # if self.supply_method == 'outsourcing' and self.hide_action_outsourcing_stock_picking:
+ # picking_ids = picking_ids.union(self.get_outsourcing_picking_ids())
+ # if picking_ids:
+ # if len(picking_ids) > 1:
+ # action['domain'] = [('id', 'in', picking_ids.ids)]
+ # elif picking_ids:
+ # action['res_id'] = picking_ids.id
+ # action['views'] = [(self.env.ref('stock.view_picking_form').id, 'form')]
+ # if 'views' in action:
+ # action['views'] += [(state, view) for state, view in action['views'] if view != 'form']
+ # return action
- def action_open_sale_order(self):
+ # def action_view_programming(self):
+ # self.ensure_one()
+ # programming_mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered(
+ # lambda p: p.programming_state == '编程中' and p.product_id.id == self.product_id.id
+ # ).mapped('programming_no')
+ # if programming_mrp_production_ids:
+ # programming_no = list(set(programming_mrp_production_ids))
+ # numbers_str = "、".join(programming_no)
+ # raise ValidationError(f"编程单号:{numbers_str},请去云平台处理")
+
+ def button_delete(self):
self.ensure_one()
- return {
- 'type': 'ir.actions.act_window',
- 'res_model': 'sale.order',
- 'res_id': self.sale_order_id.id,
- 'view_mode': 'form',
- }
+ if len(self.demand_plan_id.line_ids) == 1:
+ raise ValidationError(f"最后一条计划,不能删除!")
+ self.unlink()
- def action_open_mrp_production(self):
+ def button_batch_release_plan(self):
+ filtered_plan = self.filtered(lambda mo: mo.status == '30')
+ if not filtered_plan:
+ raise UserError(_("没有需要下达的计划!"))
+ # 按产品分组并计算总数
+ product_data = {}
+ for plan in filtered_plan:
+ if plan.product_id not in product_data:
+ # 初始化产品数据,从产品上获取需求量
+ product_data[plan.product_id] = {
+ 'plan_uom_qty': 0.0,
+ 'product_uom_qty': plan.product_uom_qty
+ }
+
+ # 累加计划数量
+ product_data[plan.product_id]['plan_uom_qty'] += plan.plan_uom_qty
+ # 检查需求超过计划数量的产品
+ warning_messages = []
+ for product, data in product_data.items():
+ if data['plan_uom_qty'] > data['product_uom_qty']:
+ warning_messages.append(
+ _("您正在下达的产品 %s,计划量%s,需求数量为%s,已超过需求数量") %
+ (product.display_name, data['plan_uom_qty'], data['product_uom_qty'])
+ )
+ if warning_messages:
+ warning_message = "\n".join(warning_messages)
+ 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': self.ids,
+ 'default_release_message': warning_message,
+ }}
+
+ def button_release_plan(self):
self.ensure_one()
- mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered(
- lambda p: p.state == 'technology_to_confirmed' and p.product_id.id == self.product_id.id
+ if not self.new_supply_method:
+ raise ValidationError(f"供货方式不能为空!")
+ if self.plan_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': self.ids,
+ 'default_release_message': f"您正在下达计划量 {self.plan_uom_qty},需求数量为 {self.product_uom_qty},已超过需求数量,是否继续?",
+ }}
+ self.action_confirm()
+
+ def action_confirm(self):
+ self = self.with_context(
+ demand_plan_line_id=self.id
)
- action = {
- 'res_model': 'mrp.production',
- 'type': 'ir.actions.act_window',
- }
- if len(mrp_production_ids) == 1:
- action.update({
- 'view_mode': 'form',
- 'res_id': mrp_production_ids.id,
- })
- else:
- action.update({
- 'name': _("制造订单列表"),
- 'domain': [('id', 'in', mrp_production_ids.ids)],
- 'view_mode': 'tree,form',
- })
- return action
-
- def action_view_purchase_request(self):
- self.ensure_one()
- pr_ids = self.env['purchase.request'].sudo().search(
- [('id', 'in', ast.literal_eval(self.outsourcing_purchase_request))])
- action = {
- 'res_model': 'purchase.request',
- 'type': 'ir.actions.act_window',
- }
- if len(pr_ids) == 1:
- action.update({
- 'view_mode': 'form',
- 'res_id': pr_ids[0].id,
- })
- else:
- action.update({
- 'name': _("采购申请"),
- 'domain': [('id', 'in', pr_ids.ids)],
- 'view_mode': 'tree,form',
- })
- return action
-
- def action_view_stock_picking(self):
- self.ensure_one()
- action = self.env["ir.actions.actions"]._for_xml_id("stock.action_picking_tree_all")
- picking_ids = None
+ self.mrp_bom_create()
+ self._action_launch_stock_rule()
if self.supply_method in ('automation', 'manual'):
- mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered(
- lambda p: p.product_id.id == self.product_id.id
- )
- picking_ids = mrp_production_ids.mapped('picking_ids').filtered(
- lambda p: p.state == 'assigned')
- elif self.supply_method in ('purchase', 'outsourcing'):
- picking_ids = self.sale_order_id.picking_ids.filtered(
- lambda
- p: p.state == 'assigned' and p.picking_type_id.name != '发料出库' and p.move_line_ids.product_id in self.product_id)
- if self.supply_method == 'outsourcing' and self.hide_action_outsourcing_stock_picking:
- picking_ids = picking_ids.union(self.get_outsourcing_picking_ids())
- if picking_ids:
- if len(picking_ids) > 1:
- action['domain'] = [('id', 'in', picking_ids.ids)]
- elif picking_ids:
- action['res_id'] = picking_ids.id
- action['views'] = [(self.env.ref('stock.view_picking_form').id, 'form')]
- if 'views' in action:
- action['views'] += [(state, view) for state, view in action['views'] if view != 'form']
- return action
+ self.write({'status': '50'})
+ else:
+ self.write({'status': '60'})
+ self.update_sale_order_state()
- def action_view_programming(self):
+ def mrp_bom_create(self):
+ if self.supply_method in ('automation', 'manual'):
+ line_ids = self.demand_plan_id.line_ids.filtered(
+ lambda p: p.supply_method in ('automation', 'manual') and p.status in ('50', '60'))
+ if line_ids:
+ self.bom_id = line_ids[0].bom_id.id
+ return
+ elif self.supply_method == 'outsourcing':
+ line_ids = self.demand_plan_id.line_ids.filtered(
+ lambda p: p.supply_method == 'outsourcing' and p.status == '60')
+ if line_ids:
+ self.bom_id = line_ids[0].bom_id.id
+ return
+ bom_type = ''
+ # 根据供货方式修改成品模板
+ if self.supply_method == 'automation':
+ bom_type = 'normal'
+ product_template_id = self.env.ref('sf_dlm.product_template_sf').sudo().product_tmpl_id
+ elif self.supply_method == 'outsourcing':
+ bom_type = 'subcontract'
+ product_template_id = self.env.ref(
+ 'jikimo_sale_multiple_supply_methods.product_template_outsourcing').sudo()
+ elif self.supply_method == 'purchase':
+ product_template_id = self.env.ref(
+ 'jikimo_sale_multiple_supply_methods.product_template_purchase').sudo()
+ elif self.supply_method == 'manual':
+ bom_type = 'normal'
+ product_template_id = self.env.ref(
+ 'jikimo_sale_multiple_supply_methods.product_template_manual_processing').sudo()
+
+ # 复制成品模板上的属性
+ self.product_id.product_tmpl_id.copy_template(product_template_id)
+
+ # 生成BOM单据编码
+ code = f"{self.product_id.default_code}-{bom_type}-{datetime.now().strftime('%Y%m%d%H%M%S')}"
+
+ order_id = self.sale_order_id
+ product = self.product_id
+ # 拼接方法需要的item结构,成品的模型数据信息就是坯料的数据信息
+ item = {
+ 'texture_code': product.materials_id.materials_no,
+ 'texture_type_code': product.materials_type_id.materials_no,
+ 'model_long': product.length,
+ 'model_width': product.width,
+ 'model_height': product.height,
+ 'blank_volume': product.model_volume,
+ 'blank_area': product.model_area,
+ 'price': product.list_price,
+ 'embryo_redundancy_id': self.sale_order_line_id.embryo_redundancy_id,
+ 'model_id': self.model_id
+ }
+ product_name = ''
+ match = re.search(r'(S\d{5}-\d+)', product.name)
+ product_seria = 0
+ # 如果匹配成功,提取结果
+ if match:
+ product_name = match.group(0)
+ # 获取成品名结尾-n的n
+ product_seria = int(product_name.split('-')[-1])
+
+ # 成品供货方式为采购则不生成bom
+ if self.supply_method != 'purchase':
+ # 当成品上带有客供料选项时,生成坯料时选择“客供料”路线
+ if self.sale_order_line_id.embryo_redundancy_id:
+ # 将成品模板的内容复制到成品上
+ customer_provided_embryo = self.env.ref(
+ 'jikimo_sale_multiple_supply_methods.product_template_embryo_customer_provided').sudo()
+ # 创建坯料,客供料的批量不需要创建bom
+ material_customer_provided_embryo = self.env['product.template'].sudo().no_bom_product_create(
+ customer_provided_embryo.with_context(active_test=False).product_variant_id,
+ item,
+ order_id, 'material_customer_provided', product_seria, product)
+ # 成品配置bom
+ product_bom_material_customer_provided = self.env['mrp.bom'].with_user(
+ self.env.ref("base.user_admin")).bom_create(
+ product, bom_type, 'product', code)
+ product_bom_material_customer_provided.with_user(
+ self.env.ref("base.user_admin")).bom_create_line_has(
+ material_customer_provided_embryo)
+ self.bom_id = product_bom_material_customer_provided.id
+ elif self.product_id.materials_type_id.gain_way == '自加工':
+ self_machining_id = self.env.ref('sf_dlm.product_embryo_sf_self_machining').sudo()
+ # 创建坯料
+ self_machining_embryo = self.env['product.template'].sudo().no_bom_product_create(
+ self_machining_id,
+ item,
+ order_id, 'self_machining', product_seria, product)
+ # 创建坯料的bom
+ self_machining_bom = self.env['mrp.bom'].with_user(
+ self.env.ref("base.user_admin")).bom_create(
+ self_machining_embryo, 'normal', False)
+ # 创建坯料里bom的组件
+ self_machining_bom_line = self_machining_bom.with_user(
+ self.env.ref("base.user_admin")).bom_create_line(
+ self_machining_embryo)
+ if not self_machining_bom_line:
+ raise UserError('该订单模型的材料型号暂未有原材料,请先配置再进行分配')
+ # 产品配置bom
+ product_bom_self_machining = self.env['mrp.bom'].with_user(
+ self.env.ref("base.user_admin")).bom_create(
+ product, bom_type, 'product', code)
+ product_bom_self_machining.with_user(self.env.ref("base.user_admin")).bom_create_line_has(
+ self_machining_embryo)
+ self.bom_id = product_bom_self_machining.id
+ elif self.product_id.materials_type_id.gain_way == '外协':
+ outsource_id = self.env.ref('sf_dlm.product_embryo_sf_outsource').sudo()
+ # 创建坯料
+ outsource_embryo = self.env['product.template'].sudo().no_bom_product_create(outsource_id,
+ item,
+ order_id,
+ 'subcontract',
+ product_seria,
+ product)
+ if outsource_embryo == -3:
+ raise UserError('该订单模型的材料型号暂未设置获取方式和供应商,请先配置再进行分配')
+ # 创建坯料的bom
+ outsource_bom = self.env['mrp.bom'].with_user(self.env.ref("base.user_admin")).bom_create(
+ outsource_embryo,
+ 'subcontract', True)
+ # 创建坯料的bom的组件
+ outsource_bom_line = outsource_bom.with_user(
+ self.env.ref("base.user_admin")).bom_create_line(outsource_embryo)
+ if not outsource_bom_line:
+ raise UserError('该订单模型的材料型号暂未有原材料,请先配置再进行分配')
+ # 产品配置bom
+ product_bom_outsource = self.env['mrp.bom'].with_user(
+ self.env.ref("base.user_admin")).bom_create(product, bom_type, 'product', code)
+ product_bom_outsource.with_user(self.env.ref("base.user_admin")).bom_create_line_has(
+ outsource_embryo)
+ self.bom_id = product_bom_outsource.id
+ elif self.product_id.materials_type_id.gain_way == '采购':
+ purchase_id = self.env.ref('sf_dlm.product_embryo_sf_purchase').sudo()
+ purchase_embryo = self.env['product.template'].sudo().no_bom_product_create(purchase_id,
+ item,
+ order_id,
+ 'purchase',
+ product_seria,
+ product)
+ if purchase_embryo and purchase_embryo == -3:
+ raise UserError('该订单模型的材料型号暂未设置获取方式和供应商,请先配置再进行分配')
+ else:
+ # 产品配置bom
+ product_bom_purchase = self.env['mrp.bom'].with_user(
+ self.env.ref("base.user_admin")).bom_create(product, bom_type, 'product', code)
+ product_bom_purchase.with_user(self.env.ref("base.user_admin")).bom_create_line_has(
+ purchase_embryo)
+ self.bom_id = product_bom_purchase.id
+
+ def _action_launch_stock_rule(self):
+ procurements = []
+ group_id = self.sale_order_id.procurement_group_id
+ if not group_id:
+ group_id = self.env['procurement.group'].create(self._prepare_procurement_group_vals())
+ self.sale_order_id.procurement_group_id = group_id
+ else:
+ updated_vals = {}
+ if group_id.partner_id != self.sale_order_id.partner_shipping_id:
+ updated_vals.update({'partner_id': self.sale_order_id.partner_shipping_id.id})
+ if group_id.move_type != self.sale_order_id.picking_policy:
+ updated_vals.update({'move_type': self.sale_order_id.picking_policy})
+ if updated_vals:
+ group_id.write(updated_vals)
+ values = self._prepare_procurement_values(group_id=group_id)
+ line_uom = self.sale_order_line_id.product_uom
+ quant_uom = self.product_id.uom_id
+ plan_uom_qty, procurement_uom = line_uom._adjust_uom_quantities(self.plan_uom_qty, quant_uom)
+ procurements.append(self.env['procurement.group'].Procurement(
+ self.product_id, plan_uom_qty, procurement_uom,
+ self.sale_order_id.partner_shipping_id.property_stock_customer,
+ self.product_id.display_name, self.sale_order_id.name, self.sale_order_id.company_id, values))
+ if procurements:
+ procurement_group = self.env['procurement.group']
+ if self.env.context.get('import_file'):
+ procurement_group = procurement_group.with_context(import_file=False)
+ procurement_group.run(procurements)
+ orders = self.mapped('sale_order_id')
+ for order in orders:
+ pickings_to_confirm = order.picking_ids.filtered(lambda p: p.state not in ['cancel', 'done'])
+ if pickings_to_confirm:
+ pickings_to_confirm.action_confirm()
+ return True
+
+ def _prepare_procurement_group_vals(self):
+ return {
+ 'name': self.sale_order_id.name,
+ 'move_type': self.sale_order_id.picking_policy,
+ 'sale_id': self.sale_order_id.id,
+ 'partner_id': self.sale_order_id.partner_shipping_id.id,
+ }
+
+ def _prepare_procurement_values(self, group_id=False):
self.ensure_one()
- programming_mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered(
- lambda p: p.programming_state == '编程中' and p.product_id.id == self.product_id.id
- ).mapped('programming_no')
- if programming_mrp_production_ids:
- programming_no = list(set(programming_mrp_production_ids))
- numbers_str = "、".join(programming_no)
- raise ValidationError(f"编程单号:{numbers_str},请去云平台处理")
+ date_deadline = self.sale_order_id.commitment_date or (
+ self.sale_order_id.date_order + timedelta(days=self.sale_order_line_id.customer_lead or 0.0))
+ date_planned = date_deadline - timedelta(days=self.sale_order_id.company_id.security_lead)
+ values = {
+ 'group_id': group_id,
+ 'sale_line_id': self.sale_order_line_id.id,
+ 'date_planned': date_planned,
+ 'date_deadline': date_deadline,
+ 'route_ids': self.route_ids,
+ 'warehouse_id': self.sale_order_id.warehouse_id or False,
+ 'partner_id': self.sale_order_id.partner_shipping_id.id,
+ 'product_description_variants': self.sale_order_line_id.with_context(
+ lang=self.sale_order_id.partner_id.lang)._get_sale_order_line_multiline_description_variants(),
+ 'company_id': self.sale_order_id.company_id,
+ 'product_packaging_id': self.sale_order_line_id.product_packaging_id,
+ 'sequence': self.sale_order_line_id.sequence,
+ 'demand_plan_line_id': self.id
+ }
+ return values
diff --git a/sf_demand_plan/models/stock_route.py b/sf_demand_plan/models/stock_route.py
index 20e4b3c9..7719d7b1 100644
--- a/sf_demand_plan/models/stock_route.py
+++ b/sf_demand_plan/models/stock_route.py
@@ -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):
diff --git a/sf_demand_plan/models/stock_rule.py b/sf_demand_plan/models/stock_rule.py
new file mode 100644
index 00000000..fe9c07eb
--- /dev/null
+++ b/sf_demand_plan/models/stock_rule.py
@@ -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
diff --git a/sf_demand_plan/security/ir.model.access.csv b/sf_demand_plan/security/ir.model.access.csv
index a1bef90a..9bd2e29b 100644
--- a/sf_demand_plan/security/ir.model.access.csv
+++ b/sf_demand_plan/security/ir.model.access.csv
@@ -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
\ No newline at end of file
diff --git a/sf_demand_plan/views/demand_plan.xml b/sf_demand_plan/views/demand_plan.xml
index 24ac8416..da16fc8a 100644
--- a/sf_demand_plan/views/demand_plan.xml
+++ b/sf_demand_plan/views/demand_plan.xml
@@ -4,10 +4,13 @@
sf.production.demand.plan
+ class="demand_plan_tree freeze-columns-before-part_number" create="false" delete="false">
@@ -20,8 +23,13 @@
-
+
+
+
+
@@ -38,32 +46,32 @@
-
-
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -72,8 +80,15 @@
-
+
+
+
+
@@ -120,17 +135,4 @@
tree
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/sf_demand_plan/views/demand_plan_info.xml b/sf_demand_plan/views/demand_plan_info.xml
new file mode 100644
index 00000000..74d9f213
--- /dev/null
+++ b/sf_demand_plan/views/demand_plan_info.xml
@@ -0,0 +1,85 @@
+
+
+ sf.demand.plan.form
+ sf.demand.plan
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sf_demand_plan/views/menu_view.xml b/sf_demand_plan/views/menu_view.xml
new file mode 100644
index 00000000..8f591e3f
--- /dev/null
+++ b/sf_demand_plan/views/menu_view.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sf_demand_plan/views/sale_order_views.xml b/sf_demand_plan/views/sale_order_views.xml
new file mode 100644
index 00000000..01908401
--- /dev/null
+++ b/sf_demand_plan/views/sale_order_views.xml
@@ -0,0 +1,29 @@
+
+
+
+ view.sale.order.form.inherit.plan
+
+ sale.order
+
+
+ True
+
+
+
+ True
+
+
+
+
+
+
+
+
diff --git a/sf_demand_plan/wizard/__init__.py b/sf_demand_plan/wizard/__init__.py
index 170f9e85..8a988a8a 100644
--- a/sf_demand_plan/wizard/__init__.py
+++ b/sf_demand_plan/wizard/__init__.py
@@ -1 +1,2 @@
from . import sf_demand_plan_print_wizard
+from . import sf_release_plan_wizard
diff --git a/sf_demand_plan/wizard/sf_release_plan_wizard.py b/sf_demand_plan/wizard/sf_release_plan_wizard.py
new file mode 100644
index 00000000..42f85819
--- /dev/null
+++ b/sf_demand_plan/wizard/sf_release_plan_wizard.py
@@ -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()
diff --git a/sf_demand_plan/wizard/sf_release_plan_wizard_views.xml b/sf_demand_plan/wizard/sf_release_plan_wizard_views.xml
new file mode 100644
index 00000000..716093b6
--- /dev/null
+++ b/sf_demand_plan/wizard/sf_release_plan_wizard_views.xml
@@ -0,0 +1,22 @@
+
+
+
+ sf.release.plan.wizard.form
+ sf.release.plan.wizard
+
+
+
+
+
\ No newline at end of file
diff --git a/sf_mrs_connect/controllers/controllers.py b/sf_mrs_connect/controllers/controllers.py
index b06c4213..a6c1fa6c 100644
--- a/sf_mrs_connect/controllers/controllers.py
+++ b/sf_mrs_connect/controllers/controllers.py
@@ -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']})