Merge branch 'refs/heads/develop' into feature/物料需求计划管理

# Conflicts:
#	sf_demand_plan/models/sf_production_demand_plan.py
#	sf_demand_plan/views/demand_plan.xml
This commit is contained in:
guanhuan
2025-07-02 08:57:20 +08:00
20 changed files with 379 additions and 362 deletions

View File

@@ -10,6 +10,7 @@
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'views/sale_order_view.xml', 'views/sale_order_view.xml',
'views/purchase_order.xml',
'views/mrp_production.xml', 'views/mrp_production.xml',
'views/purchase_request_view.xml', 'views/purchase_request_view.xml',
'wizard/purchase_request_line_make_purchase_order_view.xml', 'wizard/purchase_request_line_make_purchase_order_view.xml',

View File

@@ -16,6 +16,69 @@ class PurchaseOrder(models.Model):
('rejected', '已驳回') ('rejected', '已驳回')
], string='Status', readonly=True, index=True, copy=False, default='draft', tracking=True) ], string='Status', readonly=True, index=True, copy=False, default='draft', tracking=True)
# 成品采购订单对应的坯料采购申请单和采购订单数量
purchase_request_count = fields.Integer('子·采购申请数量', compute='_compute_purchase_request')
purchase_order_count = fields.Integer('子·采购订单数量', compute='_compute_purchase_request')
@api.depends('state')
def _compute_purchase_request(self):
for record in self:
purchase_request_ids, purchase_order_ids = record.get_purchase_request_order()
record.purchase_request_count = len(purchase_request_ids)
record.purchase_order_count = len(purchase_order_ids)
def action_view_preform_body_purchase_request(self):
self.ensure_one()
name_list = self._get_pinking_name()
purchase_request_ids = self.env['purchase.request'].search([('origin', 'in', name_list)])
action = {
'res_model': 'purchase.request',
'type': 'ir.actions.act_window',
}
if len(purchase_request_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': purchase_request_ids[0].id,
})
else:
action.update({
'name': _("子·采购申请"),
'domain': [('id', 'in', purchase_request_ids.ids)],
'view_mode': 'tree,form',
})
return action
def action_view_preform_body_purchase_order(self):
self.ensure_one()
name_list = self._get_pinking_name()
purchase_order_ids = self.env['purchase.order'].search([('origin', 'in', name_list)])
action = {
'res_model': 'purchase.order',
'type': 'ir.actions.act_window',
}
if len(purchase_order_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': purchase_order_ids[0].id,
})
else:
action.update({
'name': _("子·采购订单"),
'domain': [('id', 'in', purchase_order_ids.ids)],
'view_mode': 'tree,form',
})
return action
def get_purchase_request_order(self):
name_list = self._get_pinking_name()
purchase_request_ids = self.env['purchase.request'].search([('origin', 'in', name_list)])
purchase_order_ids = self.env['purchase.order'].search([('origin', 'in', name_list)])
return purchase_request_ids, purchase_order_ids
def _get_pinking_name(self):
return [picking_id.name for picking_id in self.picking_ids if picking_id.name]
def button_confirm(self): def button_confirm(self):
res = super(PurchaseOrder, self).button_confirm() res = super(PurchaseOrder, self).button_confirm()

View File

@@ -16,6 +16,7 @@ class PurchaseRequest(models.Model):
) )
rule_new_add = fields.Boolean('采购请求为规则创建', default=False, compute='_compute_state', store=True) rule_new_add = fields.Boolean('采购请求为规则创建', default=False, compute='_compute_state', store=True)
rule_purchase_to_request = fields.Boolean('采购单根据规则创建坯料采购申请', default=False)
@api.depends('state') @api.depends('state')
def _compute_state(self): def _compute_state(self):

View File

@@ -45,3 +45,14 @@ class StockPicking(models.Model):
(4, x.id) for x in backorder_ids.move_ids if x.product_id.id in purchase_request_lines.mapped('product_id.id') (4, x.id) for x in backorder_ids.move_ids if x.product_id.id in purchase_request_lines.mapped('product_id.id')
] ]
return res return res
def _subcontracted_produce(self, subcontract_details):
super()._subcontracted_produce(subcontract_details)
# 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved
if self:
pr_ids = self.env["purchase.request"].sudo().search(
[('origin', 'like', self.name), ('rule_purchase_to_request', '=', True), ('state', '=', 'draft')])
if pr_ids:
pr_ids.write({'need_validation': False})
pr_ids.write({"state": "approved", 'need_validation': True, 'rule_new_add': False})

View File

@@ -26,7 +26,7 @@ class StockRule(models.Model):
request_data = rule._prepare_purchase_request( request_data = rule._prepare_purchase_request(
procurement.origin, procurement.values procurement.origin, procurement.values
) )
request_data.update({'rule_new_add': True}) request_data = self._update_request_data(procurement, request_data)
pr = purchase_request_model.create(request_data) pr = purchase_request_model.create(request_data)
cache[domain] = pr cache[domain] = pr
elif ( elif (
@@ -44,6 +44,18 @@ class StockRule(models.Model):
request_line_data.update({'origin': procurement.origin}) request_line_data.update({'origin': procurement.origin})
purchase_request_line_model.create(request_line_data) purchase_request_line_model.create(request_line_data)
def _update_request_data(self, procurement, request_data):
sp = self.env['stock.picking'].sudo().search([('name', '=', procurement.origin)])
if len(sp) == 1:
po = self.env['purchase.order'].sudo().search(
[('name', '=', sp.origin), ('purchase_type', '=', 'outsourcing')])
if po:
request_data.update({'rule_purchase_to_request': True})
else:
request_data.update({'rule_new_add': True})
return request_data
def _run_buy(self, procurements): def _run_buy(self, procurements):
# 如果补货组相同,并且产品相同,则合并 # 如果补货组相同,并且产品相同,则合并
procurements_dict = defaultdict() procurements_dict = defaultdict()

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="purchase_order_form_jikimo_purchase_request" model="ir.ui.view">
<field name="name">purchase.order.inherited.form.jikimo.purchase.request</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="mrp_subcontracting_purchase.purchase_order_form_mrp_subcontracting_purchase"/>
<field name="arch" type="xml">
<xpath expr="//div[hasclass('oe_button_box')]/button[@name='action_view_subcontracting_resupply']" position="before">
<button
class="oe_stat_button" name="action_view_preform_body_purchase_order"
type="object" icon="fa-truck" attrs="{'invisible': [('purchase_order_count', '=', 0)]}" groups="stock.group_stock_user">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="purchase_order_count"/></span>
<span class="o_stat_text">子·采购订单</span>
</div>
</button>
<button
class="oe_stat_button" name="action_view_preform_body_purchase_request"
type="object" icon="fa-truck" attrs="{'invisible': [('purchase_request_count', '=', 0)]}" groups="stock.group_stock_user">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="purchase_request_count"/></span>
<span class="o_stat_text">子·采购申请</span>
</div>
</button>
</xpath>
</field>
</record>
</odoo>

View File

@@ -67,6 +67,16 @@
<field name="part_number"/> <field name="part_number"/>
<field name="part_name" invisible="1"/> <field name="part_name" invisible="1"/>
</xpath> </xpath>
<xpath expr="//tree" position="inside">
<header>
<button
name="%(purchase_request.action_purchase_request_line_make_purchase_order)d"
string="创建询价单"
type="action"
class="btn-primary"
/>
</header>
</xpath>
</field> </field>
</record> </record>

View File

@@ -79,6 +79,7 @@ class SfProductionDemandPlan(models.Model):
model_long = fields.Char('尺寸(mm)', compute='_compute_model_long') model_long = fields.Char('尺寸(mm)', compute='_compute_model_long')
blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类', blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类',
related='product_id.blank_type') related='product_id.blank_type')
blank_precision = fields.Selection([('精坯', '精坯'), ('粗坯', '粗坯')], string='坯料类型', related='product_id.blank_precision')
embryo_long = fields.Char('坯料尺寸(mm)', related='demand_plan_id.embryo_long') embryo_long = fields.Char('坯料尺寸(mm)', related='demand_plan_id.embryo_long')
materials_id = fields.Char('材料', related='demand_plan_id.materials_id') materials_id = fields.Char('材料', related='demand_plan_id.materials_id')
model_machining_precision = fields.Selection(selection=_get_machining_precision, string='精度', model_machining_precision = fields.Selection(selection=_get_machining_precision, string='精度',

View File

@@ -82,3 +82,8 @@
cursor: pointer; cursor: pointer;
} }
} }
th[data-name=processing_time] + th::before{
content: '待执行单据';
line-height: 38px;
}

View File

@@ -4,14 +4,13 @@
<field name="model">sf.production.demand.plan</field> <field name="model">sf.production.demand.plan</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="需求计划" default_order="sequence desc,id desc" editable="bottom" <tree string="需求计划" default_order="sequence desc,id desc" editable="bottom"
class="demand_plan_tree" create="false" delete="false"> class="demand_plan_tree freeze-columns-before-part_number" create="false" delete="false">
<header> <header>
<button string="打印" name="button_action_print" type="object" <button string="打印" name="button_action_print" type="object"
class="btn-primary"/> class="btn-primary"/>
</header> </header>
<field name="sequence" widget="handle"/> <field name="sequence" widget="handle"/>
<field name="id" optional="hide"/> <field name="id" optional="hide"/>
<field name="priority"/>
<field name="status"/> <field name="status"/>
<field name="customer_name"/> <field name="customer_name"/>
<field name="order_remark"/> <field name="order_remark"/>
@@ -29,6 +28,7 @@
<field name="qty_to_deliver"/> <field name="qty_to_deliver"/>
<field name="model_long"/> <field name="model_long"/>
<field name="blank_type" optional="hide"/> <field name="blank_type" optional="hide"/>
<field name="blank_precision"/>
<field name="embryo_long"/> <field name="embryo_long"/>
<field name="materials_id"/> <field name="materials_id"/>
<field name="model_machining_precision"/> <field name="model_machining_precision"/>
@@ -44,7 +44,10 @@
<field name="date_order"/> <field name="date_order"/>
<field name="contract_code"/> <field name="contract_code"/>
<field name="plan_remark" attrs="{'readonly': [('status', 'in', ('60','100'))]}"/> <field name="plan_remark" attrs="{'readonly': [('status', 'in', ('60','100'))]}"/>
<field name="processing_time"/> <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="material_check" optional="hide"/>
<!-- <field name="hide_action_open_mrp_production" invisible="1"/>--> <!-- <field name="hide_action_open_mrp_production" invisible="1"/>-->
<!-- <field name="hide_action_purchase_orders" invisible="1"/>--> <!-- <field name="hide_action_purchase_orders" invisible="1"/>-->
@@ -63,6 +66,7 @@
<field name="planned_start_date" attrs="{'readonly': [('status', 'in', ('60','100'))]}"/> <field name="planned_start_date" attrs="{'readonly': [('status', 'in', ('60','100'))]}"/>
<field name="actual_start_date"/> <field name="actual_start_date"/>
<field name="actual_end_date"/> <field name="actual_end_date"/>
<field name="processing_time"/>
<field name="create_date" optional="hide" string="创建时间"/> <field name="create_date" optional="hide" string="创建时间"/>
<field name="create_uid" optional="hide" string="创建人"/> <field name="create_uid" optional="hide" string="创建人"/>
<field name="write_date" string="更新时间"/> <field name="write_date" string="更新时间"/>

View File

@@ -3,6 +3,7 @@ import logging
import re import re
from odoo import models, fields, api from odoo import models, fields, api
from odoo.exceptions import ValidationError
class ResProductCategory(models.Model): class ResProductCategory(models.Model):
@@ -47,11 +48,14 @@ class ResMrpBomMo(models.Model):
item.subcontractor_name = '' item.subcontractor_name = ''
def bom_create_line_has(self, embryo): def bom_create_line_has(self, embryo):
product = self.product_tmpl_id
if product.unit_number in (0, None, False):
raise ValidationError(f'产品{product.name}单件用量的值不能为{product.unit_number}')
vals = { vals = {
'bom_id': self.id, 'bom_id': self.id,
'product_id': embryo.id, 'product_id': embryo.id,
'product_tmpl_id': embryo.product_tmpl_id.id, 'product_tmpl_id': embryo.product_tmpl_id.id,
'product_qty': 1, 'product_qty': product.unit_number,
'product_uom_id': 1 'product_uom_id': 1
} }
return self.env['mrp.bom.line'].sudo().create(vals) return self.env['mrp.bom.line'].sudo().create(vals)
@@ -122,7 +126,7 @@ class ResMrpBomMo(models.Model):
# 查bom的原材料 # 查bom的原材料
def get_raw_bom(self, product): def get_raw_bom(self, product):
raw_bom = self.env['product.product'].search( raw_bom = self.env['product.product'].search(
[('categ_id.type', '=', '原材料'), ('materials_type_id', '=', product.materials_type_id.id)],limit=1) [('categ_id.type', '=', '原材料'), ('materials_type_id', '=', product.materials_type_id.id)], limit=1)
return raw_bom return raw_bom

View File

@@ -95,6 +95,8 @@
<page string="加工参数"> <page string="加工参数">
<group> <group>
<group string="模型"> <group string="模型">
<field name="blank_type" readonly="1"/>
<field name="blank_precision" readonly="1"/>
<label for="model_long" string="坯料尺寸[mm]"/> <label for="model_long" string="坯料尺寸[mm]"/>
<div class="o_address_format"> <div class="o_address_format">
<label for="model_long" string="长"/> <label for="model_long" string="长"/>
@@ -104,7 +106,7 @@
<label for="model_height" string="高"/> <label for="model_height" string="高"/>
<field name="model_height" class="o_address_zip"/> <field name="model_height" class="o_address_zip"/>
</div> </div>
<field name="blank_type" readonly="1"/> <field name="unit_number" readonly="1"/>
<field name="model_volume" string="体积[mm³]"/> <field name="model_volume" string="体积[mm³]"/>
<field name="product_model_type_id" string="模型类型"/> <field name="product_model_type_id" string="模型类型"/>
<field name="model_processing_panel" placeholder="例如R,U" string="加工面板" <field name="model_processing_panel" placeholder="例如R,U" string="加工面板"

View File

@@ -1710,76 +1710,7 @@ class MrpProduction(models.Model):
else: else:
vals['procurement_group_id'] = is_custemer_group_id[key] vals['procurement_group_id'] = is_custemer_group_id[key]
productions = super(MrpProduction, self).create(vals_list) return super(MrpProduction, self).create(vals_list)
# 查询成品工序排序(自动化产线加工),供后续使用
product_model_type_routing_sorts = self.env['sf.product.model.type.routing.sort'].search([], order='sequence asc')
# 查询成品工序排序(人工线下加工),供后续使用
manual_product_model_type_routing_sorts = self.env['sf.manual.product.model.type.routing.sort'].search([], order='sequence asc')
# 查询坯料工序排序,供后续使用
embryo_model_type_routing_sorts = self.env['sf.embryo.model.type.routing.sort'].search([], order='sequence asc')
for production in productions:
# 生成序列号
production.action_generate_serial()
# 创建工序模板
technology_design_values = []
i = 0
if production.product_id.categ_id.type == '成品':
# 根据加工面板的面数及成品工序模板生成工序设计
if production.production_type == '自动化产线加工':
product_routing_workcenter = product_model_type_routing_sorts.filtered(
lambda s: s.product_model_type_id.id == production.product_id.product_model_type_id.id
)
else:
product_routing_workcenter = manual_product_model_type_routing_sorts.filtered(
lambda s: s.manual_product_model_type_id.id == production.product_id.product_model_type_id.id
)
if production.production_type == '自动化产线加工':
for k in (production.product_id.model_processing_panel.split(',')):
for route in product_routing_workcenter:
i += 1
technology_design_values.append(
self.env['sf.technology.design'].json_technology_design_str(k, route, i, False)
)
elif production.production_type == '人工线下加工':
for route in product_routing_workcenter:
i += 1
technology_design_values.append(
self.env['sf.technology.design'].json_technology_design_str('ZM', route, i, False)
)
else:
for route in product_routing_workcenter:
i += 1
technology_design_values.append(
self.env['sf.technology.design'].json_technology_design_str(False, route, i, False)
)
elif production.product_id.categ_id.type == '坯料':
embryo_routing_workcenter = embryo_model_type_routing_sorts.filtered(
lambda s: s.embryo_model_type_id.id == production.product_id.embryo_model_type_id.id
)
for route_embryo in embryo_routing_workcenter:
i += 1
technology_design_values.append(
self.env['sf.technology.design'].json_technology_design_str(False, route_embryo, i, False)
)
# 处理表面工艺
for item in production.product_id.product_model_type_id.surface_technics_routing_tmpl_ids:
if item.route_workcenter_id.surface_technics_id.id:
for process_param in production.product_id.model_process_parameters_ids:
if item.route_workcenter_id.surface_technics_id == process_param.process_id:
technology_design_values.append(
self.env['sf.technology.design'].json_technology_design_str(
False,
item.route_workcenter_id,
i,
process_param
)
)
production.technology_design_ids = technology_design_values
# 设置制造订单状态为待工艺确认
productions.write({'state': 'technology_to_confirmed'})
return productions
@api.depends('procurement_group_id.stock_move_ids.created_purchase_line_id.order_id', @api.depends('procurement_group_id.stock_move_ids.created_purchase_line_id.order_id',
'procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id') 'procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id')

View File

@@ -27,13 +27,15 @@ class ResProductMo(models.Model):
categ_type = fields.Selection(string='产品的类别', related='categ_id.type', store=True) categ_type = fields.Selection(string='产品的类别', related='categ_id.type', store=True)
model_name = fields.Char('模型名称') model_name = fields.Char('模型名称')
blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类') blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类')
blank_precision = fields.Selection([('精坯', '精坯'), ('粗坯', '粗坯')], string='坯料类型')
model_long = fields.Float('模型长(mm)', digits=(16, 3)) model_long = fields.Float('模型长(mm)', digits=(16, 3))
model_width = fields.Float('模型宽(mm)', digits=(16, 3)) model_width = fields.Float('模型宽(mm)', digits=(16, 3))
model_height = fields.Float('模型高(mm)', digits=(16, 3)) model_height = fields.Float('模型高(mm)', digits=(16, 3))
unit_number = fields.Float('单件用量', digits=(16, 3), default=1)
model_volume = fields.Float('模型体积(m³)') model_volume = fields.Float('模型体积(m³)')
model_area = fields.Float('模型表面积(m²)') model_area = fields.Float('模型表面积(m²)')
model_machining_precision = fields.Selection(selection=_get_machining_precision, string='加工精度') model_machining_precision = fields.Selection(selection=_get_machining_precision, string='加工精度')
model_processing_panel = fields.Char('模型加工面板') model_processing_panel = fields.Char('模型加工面板', default='')
model_remark = fields.Char('模型备注说明') model_remark = fields.Char('模型备注说明')
length = fields.Float('长(mm)', digits=(16, 3)) length = fields.Float('长(mm)', digits=(16, 3))
width = fields.Float('宽(mm)', digits=(16, 3)) width = fields.Float('宽(mm)', digits=(16, 3))
@@ -901,15 +903,17 @@ class ResProductMo(models.Model):
vals = { vals = {
'name': product_name, 'name': product_name,
'blank_type': item.get('blank_type'), 'blank_type': item.get('blank_type'),
'blank_precision': item.get('blank_precision'),
'model_long': item.get('blank_length') if blank_bool else self.format_float(item['model_long'] + embryo_redundancy_id.long), 'model_long': item.get('blank_length') if blank_bool else self.format_float(item['model_long'] + embryo_redundancy_id.long),
'model_width': item.get('blank_width') if blank_bool else self.format_float(item['model_width'] + embryo_redundancy_id.width), 'model_width': item.get('blank_width') if blank_bool else self.format_float(item['model_width'] + embryo_redundancy_id.width),
'model_height': item.get('blank_height') if blank_bool else self.format_float(item['model_height'] + embryo_redundancy_id.height), 'model_height': item.get('blank_height') if blank_bool else self.format_float(item['model_height'] + embryo_redundancy_id.height),
'unit_number': item.get('unit_number'),
'model_volume': self.format_float(((item['model_long'] + embryo_redundancy_id.long) * 'model_volume': self.format_float(((item['model_long'] + embryo_redundancy_id.long) *
(item['model_width'] + embryo_redundancy_id.width) * (item['model_width'] + embryo_redundancy_id.width) *
(item['model_height'] + embryo_redundancy_id.height))) if not blank_bool else ( (item['model_height'] + embryo_redundancy_id.height))) if not blank_bool else (
item.get('blank_length') * item.get('blank_width') * item.get('blank_height')), item.get('blank_length') * item.get('blank_width') * item.get('blank_height')),
'product_model_type_id': model_type.id, 'product_model_type_id': model_type.id,
'model_processing_panel': item['processing_panel_detail'], 'model_processing_panel': item['processing_panel_detail'] if item['processing_panel_detail'] else '',
'model_machining_precision': item['model_machining_precision'], 'model_machining_precision': item['model_machining_precision'],
'model_code': item['barcode'], 'model_code': item['barcode'],
'length': item['model_long'], 'length': item['model_long'],

View File

@@ -21,6 +21,7 @@ from odoo.addons.sf_base.commons.common import Common
from odoo.exceptions import UserError from odoo.exceptions import UserError
from io import BytesIO from io import BytesIO
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from dateutil.relativedelta import relativedelta
class stockWarehouse(models.Model): class stockWarehouse(models.Model):
@@ -163,203 +164,203 @@ class StockRule(models.Model):
[('res_id', '=', res_id), ('res_field', '=', res_field)], limit=1) [('res_id', '=', res_id), ('res_field', '=', res_field)], limit=1)
attachment_info.write({'name': name}) attachment_info.write({'name': name})
# @api.model @api.model
# def _run_manufacture(self, procurements): def _run_manufacture(self, procurements):
# productions_values_by_company = defaultdict(list) productions_values_by_company = defaultdict(list)
# errors = [] errors = []
# for procurement, rule in procurements: for procurement, rule in procurements:
# if float_compare(procurement.product_qty, 0, precision_rounding=procurement.product_uom.rounding) <= 0: if float_compare(procurement.product_qty, 0, precision_rounding=procurement.product_uom.rounding) <= 0:
# # If procurement contains negative quantity, don't create a MO that would be for a negative value. # If procurement contains negative quantity, don't create a MO that would be for a negative value.
# continue continue
# bom = rule._get_matching_bom(procurement.product_id, procurement.company_id, procurement.values) bom = rule._get_matching_bom(procurement.product_id, procurement.company_id, procurement.values)
# productions_values_by_company[procurement.company_id.id].append(rule._prepare_mo_vals(*procurement, bom)) productions_values_by_company[procurement.company_id.id].append(rule._prepare_mo_vals(*procurement, bom))
# if errors: if errors:
# raise ProcurementException(errors) raise ProcurementException(errors)
# for company_id, productions_values in productions_values_by_company.items(): for company_id, productions_values in productions_values_by_company.items():
# # create the MO as SUPERUSER because the current user may not have the rights to do it # create the MO as SUPERUSER because the current user may not have the rights to do it
# # (mto product launched by a sale for example) # (mto product launched by a sale for example)
# '''创建制造订单''' '''创建制造订单'''
# productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create( productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(
# productions_values) productions_values)
# ''' '''
# 创建工单 创建工单
# ''' '''
# # productions._create_workorder() # productions._create_workorder()
# # #
# # self.env['stock.move'].sudo().create(productions._get_moves_finished_values()) # self.env['stock.move'].sudo().create(productions._get_moves_finished_values())
# productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \ productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \
# ( (
# p.move_dest_ids.procure_method != 'make_to_order' and not p.move_dest_ids.procure_method != 'make_to_order' and not
# p.move_raw_ids and not p.workorder_ids)).action_confirm() p.move_raw_ids and not p.workorder_ids)).action_confirm()
# # 处理 根据制造订单生成的采购单坯料入库时到原材料库,手动将原材料位置该为坯料存货区 # 处理 根据制造订单生成的采购单坯料入库时到原材料库,手动将原材料位置该为坯料存货区
# for production in productions: for production in productions:
# if production.picking_ids: if production.picking_ids:
# product_type_id = production.picking_ids[0].move_ids[0].product_id.categ_id product_type_id = production.picking_ids[0].move_ids[0].product_id.categ_id
# if product_type_id.name == '坯料': if product_type_id.name == '坯料':
# location_id = self.env['stock.location'].search([('name', '=', '坯料存货区')]) location_id = self.env['stock.location'].search([('name', '=', '坯料存货区')])
# if not location_id: if not location_id:
# logging.info(f'没有搜索到【坯料存货区】: {location_id}') logging.info(f'没有搜索到【坯料存货区】: {location_id}')
# break break
# for picking_id in production.picking_ids: for picking_id in production.picking_ids:
# if picking_id.picking_type_id.name == '内部调拨': if picking_id.picking_type_id.name == '内部调拨':
# if picking_id.location_dest_id.product_type != product_type_id: if picking_id.location_dest_id.product_type != product_type_id:
# picking_id.location_dest_id = location_id.id picking_id.location_dest_id = location_id.id
# elif picking_id.picking_type_id.name == '生产发料': elif picking_id.picking_type_id.name == '生产发料':
# if picking_id.location_id.product_type != product_type_id: if picking_id.location_id.product_type != product_type_id:
# picking_id.location_id = location_id.id picking_id.location_id = location_id.id
# for production in productions: for production in productions:
# ''' '''
# 创建制造订单时生成序列号 创建制造订单时生成序列号
# ''' '''
# # production.action_generate_serial() production.action_generate_serial()
# origin_production = production.move_dest_ids and production.move_dest_ids[ origin_production = production.move_dest_ids and production.move_dest_ids[
# 0].raw_material_production_id or False 0].raw_material_production_id or False
# orderpoint = production.orderpoint_id orderpoint = production.orderpoint_id
# if orderpoint and orderpoint.create_uid.id == SUPERUSER_ID and orderpoint.trigger == 'manual': if orderpoint and orderpoint.create_uid.id == SUPERUSER_ID and orderpoint.trigger == 'manual':
# production.message_post( production.message_post(
# body=_('This production order has been created from Replenishment Report.'), body=_('This production order has been created from Replenishment Report.'),
# message_type='comment', message_type='comment',
# subtype_xmlid='mail.mt_note') subtype_xmlid='mail.mt_note')
# elif orderpoint: elif orderpoint:
# production.message_post_with_view( production.message_post_with_view(
# 'mail.message_origin_link', 'mail.message_origin_link',
# values={'self': production, 'origin': orderpoint}, values={'self': production, 'origin': orderpoint},
# subtype_id=self.env.ref('mail.mt_note').id) subtype_id=self.env.ref('mail.mt_note').id)
# elif origin_production: elif origin_production:
# production.message_post_with_view( production.message_post_with_view(
# 'mail.message_origin_link', 'mail.message_origin_link',
# values={'self': production, 'origin': origin_production}, values={'self': production, 'origin': origin_production},
# subtype_id=self.env.ref('mail.mt_note').id) subtype_id=self.env.ref('mail.mt_note').id)
# ''' '''
# 创建生产计划 创建生产计划
# ''' '''
# # 工单耗时 # 工单耗时
# # workorder_duration = 0 workorder_duration = 0
# # for workorder in production.workorder_ids: for workorder in production.workorder_ids:
# # workorder_duration += workorder.duration_expected workorder_duration += workorder.duration_expected
# # sale_order = self.env['sale.order'].sudo().search([('name', '=', production.origin)]) sale_order = self.env['sale.order'].sudo().search([('name', '=', production.origin)])
# # # 如果订单为空,则获取来源制造订单的销售单 # 如果订单为空,则获取来源制造订单的销售单
# # if not sale_order: if not sale_order:
# # mrp_production = self.env['mrp.production'].sudo().search([('name', '=', production.origin)], mrp_production = self.env['mrp.production'].sudo().search([('name', '=', production.origin)],
# # limit=1) limit=1)
# # if mrp_production: if mrp_production:
# # sale_order = self.env['sale.order'].sudo().search([('name', '=', mrp_production.origin)]) sale_order = self.env['sale.order'].sudo().search([('name', '=', mrp_production.origin)])
# # else: else:
# # mrp_production = production mrp_production = production
# # # if sale_order: # if sale_order:
# # # sale_order.write({'schedule_status': 'to schedule'}) # sale_order.write({'schedule_status': 'to schedule'})
# # self.env['sf.production.plan'].sudo().with_company(company_id).create({ self.env['sf.production.plan'].sudo().with_company(company_id).create({
# # 'name': production.name, 'name': production.name,
# # 'order_deadline': sale_order.deadline_of_delivery, 'order_deadline': sale_order.deadline_of_delivery,
# # 'production_id': production.id, 'production_id': production.id,
# # 'date_planned_start': production.date_planned_start, 'date_planned_start': production.date_planned_start,
# # 'origin': mrp_production.origin, 'origin': mrp_production.origin,
# # 'product_qty': production.product_qty, 'product_qty': production.product_qty,
# # 'product_id': production.product_id.id, 'product_id': production.product_id.id,
# # 'state': 'draft', 'state': 'draft',
# # }) })
# all_production = productions all_production = productions
# grouped_product_ids = {k: list(g) for k, g in groupby(all_production, key=lambda x: x.product_id.id)} grouped_product_ids = {k: list(g) for k, g in groupby(all_production, key=lambda x: x.product_id.id)}
# # 初始化一个字典来存储每个product_id对应的生产订单名称列表 # 初始化一个字典来存储每个product_id对应的生产订单名称列表
# product_id_to_production_names = {} product_id_to_production_names = {}
# # 对于每个product_id获取其所有生产订单的名称 # 对于每个product_id获取其所有生产订单的名称
# for product_id, all_production in grouped_product_ids.items(): for product_id, all_production in grouped_product_ids.items():
# # 为同一个product_id创建一个生产订单名称列表 # 为同一个product_id创建一个生产订单名称列表
# product_id_to_production_names[product_id] = [production.name for production in all_production] product_id_to_production_names[product_id] = [production.name for production in all_production]
# for production_item in productions: for production_item in productions:
# technology_design_values = [] technology_design_values = []
# # production_programming = self.env['mrp.production'].search( production_programming = self.env['mrp.production'].search(
# # [('product_id.id', '=', production_item.product_id.id), [('product_id.id', '=', production_item.product_id.id),
# # ('origin', '=', production_item.origin)], ('origin', '=', production_item.origin)],
# # limit=1, order='id asc') limit=1, order='id asc')
# # if production_item.product_id.id in product_id_to_production_names: if production_item.product_id.id in product_id_to_production_names:
# # # 同一个产品多个制造订单对应一个编程单和模型库 # 同一个产品多个制造订单对应一个编程单和模型库
# # # 只调用一次fetchCNC并将所有生产订单的名称作为字符串传递 # 只调用一次fetchCNC并将所有生产订单的名称作为字符串传递
# # if not production_item.programming_no and production_item.production_type in ['自动化产线加工', if not production_item.programming_no and production_item.production_type in ['自动化产线加工',
# # '人工线下加工']: '人工线下加工']:
# # if not production_programming.programming_no: if not production_programming.programming_no:
# # production_item.fetchCNC( production_item.fetchCNC(
# # ', '.join(product_id_to_production_names[production_item.product_id.id])) ', '.join(product_id_to_production_names[production_item.product_id.id]))
# # else: else:
# # production_item.write({'programming_no': production_programming.programming_no, production_item.write({'programming_no': production_programming.programming_no,
# # 'programming_state': '编程中'}) 'programming_state': '编程中'})
# i = 0 i = 0
# if production_item.product_id.categ_id.type == '成品': if production_item.product_id.categ_id.type == '成品':
# # 根据加工面板的面数及成品工序模板生成工序设计 # 根据加工面板的面数及成品工序模板生成工序设计
# if production_item.production_type == '自动化产线加工': if production_item.production_type == '自动化产线加工':
# model = 'sf.product.model.type.routing.sort' model = 'sf.product.model.type.routing.sort'
# domain = [ domain = [
# ('product_model_type_id', '=', production_item.product_id.product_model_type_id.id)] ('product_model_type_id', '=', production_item.product_id.product_model_type_id.id)]
# else: else:
# model = 'sf.manual.product.model.type.routing.sort' model = 'sf.manual.product.model.type.routing.sort'
# domain = [('manual_product_model_type_id', '=', domain = [('manual_product_model_type_id', '=',
# production_item.product_id.product_model_type_id.id)] production_item.product_id.product_model_type_id.id)]
# product_routing_workcenter = self.env[model].search(domain, order='sequence asc') product_routing_workcenter = self.env[model].search(domain, order='sequence asc')
# if production_item.production_type == '自动化产线加工': if production_item.production_type == '自动化产线加工':
# for k in (production_item.product_id.model_processing_panel.split(',')): for k in (production_item.product_id.model_processing_panel.split(',')):
# for route in product_routing_workcenter: for route in product_routing_workcenter:
# i += 1 i += 1
# technology_design_values.append( technology_design_values.append(
# self.env['sf.technology.design'].json_technology_design_str(k, route, i, False)) self.env['sf.technology.design'].json_technology_design_str(k, route, i, False))
# elif production_item.production_type == '人工线下加工': elif production_item.production_type == '人工线下加工':
# for route in product_routing_workcenter: for route in product_routing_workcenter:
# i += 1 i += 1
# technology_design_values.append( technology_design_values.append(
# self.env['sf.technology.design'].json_technology_design_str('ZM', route, i, False)) self.env['sf.technology.design'].json_technology_design_str('ZM', route, i, False))
# else: else:
# for route in product_routing_workcenter: for route in product_routing_workcenter:
# i += 1 i += 1
# technology_design_values.append( technology_design_values.append(
# self.env['sf.technology.design'].json_technology_design_str(False, route, i, False)) self.env['sf.technology.design'].json_technology_design_str(False, route, i, False))
# elif production_item.product_id.categ_id.type == '坯料': elif production_item.product_id.categ_id.type == '坯料':
# embryo_routing_workcenter = self.env['sf.embryo.model.type.routing.sort'].search( embryo_routing_workcenter = self.env['sf.embryo.model.type.routing.sort'].search(
# [('embryo_model_type_id', '=', production_item.product_id.embryo_model_type_id.id)], [('embryo_model_type_id', '=', production_item.product_id.embryo_model_type_id.id)],
# order='sequence asc' order='sequence asc'
# ) )
# for route_embryo in embryo_routing_workcenter: for route_embryo in embryo_routing_workcenter:
# i += 1 i += 1
# technology_design_values.append( technology_design_values.append(
# self.env['sf.technology.design'].json_technology_design_str(False, route_embryo, i, self.env['sf.technology.design'].json_technology_design_str(False, route_embryo, i,
# False)) False))
# surface_technics_arr = [] surface_technics_arr = []
# route_workcenter_arr = [] route_workcenter_arr = []
# for item in production_item.product_id.product_model_type_id.surface_technics_routing_tmpl_ids: for item in production_item.product_id.product_model_type_id.surface_technics_routing_tmpl_ids:
# if item.route_workcenter_id.surface_technics_id.id: if item.route_workcenter_id.surface_technics_id.id:
# for process_param in production_item.product_id.model_process_parameters_ids: for process_param in production_item.product_id.model_process_parameters_ids:
# if item.route_workcenter_id.surface_technics_id == process_param.process_id: if item.route_workcenter_id.surface_technics_id == process_param.process_id:
# surface_technics_arr.append( surface_technics_arr.append(
# item.route_workcenter_id.surface_technics_id.id) item.route_workcenter_id.surface_technics_id.id)
# route_workcenter_arr.append(item.route_workcenter_id.id) route_workcenter_arr.append(item.route_workcenter_id.id)
# if surface_technics_arr: if surface_technics_arr:
# production_process = self.env['sf.production.process'].search( production_process = self.env['sf.production.process'].search(
# [('id', 'in', surface_technics_arr)], [('id', 'in', surface_technics_arr)],
# order='sequence asc' order='sequence asc'
# ) )
# for p in production_process: for p in production_process:
# logging.info('production_process:%s' % p.name) logging.info('production_process:%s' % p.name)
# process_parameters = production_item.product_id.model_process_parameters_ids.filtered( process_parameters = production_item.product_id.model_process_parameters_ids.filtered(
# lambda pm: pm.process_id.id == p.id) lambda pm: pm.process_id.id == p.id)
# for process_parameter in process_parameters: for process_parameter in process_parameters:
# i += 1 i += 1
# route_production_process = self.env[ route_production_process = self.env[
# 'mrp.routing.workcenter'].search( 'mrp.routing.workcenter'].search(
# [('surface_technics_id', '=', p.id), [('surface_technics_id', '=', p.id),
# ('id', 'in', route_workcenter_arr)]) ('id', 'in', route_workcenter_arr)])
# technology_design_values.append( technology_design_values.append(
# self.env['sf.technology.design'].json_technology_design_str(False, self.env['sf.technology.design'].json_technology_design_str(False,
# route_production_process, route_production_process,
# i, i,
# process_parameter)) process_parameter))
# production_item.technology_design_ids = technology_design_values production_item.technology_design_ids = technology_design_values
# productions.write({'state': 'technology_to_confirmed'}) productions.write({'state': 'technology_to_confirmed'})
# return True return True
class ProductionLot(models.Model): class ProductionLot(models.Model):
@@ -727,6 +728,33 @@ class StockPicking(models.Model):
production.workorder_ids.write({'back_button_display': False}) production.workorder_ids.write({'back_button_display': False})
return res return res
def _prepare_subcontract_mo_vals(self, subcontract_move, bom):
subcontract_move.ensure_one()
group = self.env['procurement.group'].sudo().search([('name', '=', self.name)])
if not group:
group = self.env['procurement.group'].create({
'name': self.name,
'partner_id': self.partner_id.id,
})
product = subcontract_move.product_id
warehouse = self._get_warehouse(subcontract_move)
vals = {
'company_id': subcontract_move.company_id.id,
'procurement_group_id': group.id,
'subcontractor_id': subcontract_move.picking_id.partner_id.commercial_partner_id.id,
'picking_ids': [subcontract_move.picking_id.id],
'product_id': product.id,
'product_uom_id': subcontract_move.product_uom.id,
'bom_id': bom.id,
'location_src_id': subcontract_move.picking_id.partner_id.with_company(subcontract_move.company_id).property_stock_subcontractor.id,
'location_dest_id': subcontract_move.picking_id.partner_id.with_company(subcontract_move.company_id).property_stock_subcontractor.id,
'product_qty': subcontract_move.product_uom_qty,
'picking_type_id': warehouse.subcontracting_type_id.id,
'date_planned_start': subcontract_move.date - relativedelta(days=product.produce_delay)
}
return vals
# 创建 外协出库入单 # 创建 外协出库入单
def create_outcontract_picking(self, workorders, item, sorted_workorders): def create_outcontract_picking(self, workorders, item, sorted_workorders):
production = workorders[0].production_id production = workorders[0].production_id

View File

@@ -2,4 +2,3 @@ from . import ftp_operate
from . import res_config_setting from . import res_config_setting
from . import sync_common from . import sync_common
from . import order_price from . import order_price
from . import mrp_production

View File

@@ -1,46 +0,0 @@
from itertools import groupby
from odoo import models, api
from odoo.tools.misc import OrderedSet
class MrpProduction(models.Model):
_inherit = 'mrp.production'
@api.model_create_multi
def create(self, vals_list):
"""
生成编程单
"""
productions = super().create(vals_list)
if not productions:
return self.browse()
# 定义变量存储编程单
grouped_product_programming_no = {}
# 定义产品拼接成的制造订单名称
grouped_product_production_name = {}
# 查出所有的制造订单,为了适配通过补货生成的制造订单
all_productions = self.env['mrp.production'].search([('origin', '=', productions[0].origin)])
# 将不同产品的制造订单进行分组
grouped_product_productions = {k: list(g) for k, g in groupby(all_productions, key=lambda x: x.product_id.id)}
for product_id, grouped_productions in grouped_product_productions.items():
# 产品对应的编程单号
if product_id not in grouped_product_programming_no:
# 使用列表推导式获取非空的programming_no
programming_nos = [p.programming_no for p in grouped_productions if p.programming_no if p.programming_no is not False]
if programming_nos:
grouped_product_programming_no[product_id] = programming_nos[0]
grouped_product_production_name[product_id] = ','.join(list(map(lambda p:p.name, grouped_productions)))
# 同一个产品的制造订单只请求一次CNC编程
for production in productions:
if not production.programming_no and production.production_type in ['自动化产线加工','人工线下加工']:
if production.product_id.id not in grouped_product_programming_no:
production.fetchCNC(grouped_product_production_name[production.product_id.id])
grouped_product_programming_no[production.product_id.id] = production.programming_no
else:
production.write({
'programming_no': grouped_product_programming_no[production.product_id.id],
'programming_state': '编程中'
})
return productions

View File

@@ -3,4 +3,3 @@
from . import custom_plan from . import custom_plan
from . import change_manufactuing from . import change_manufactuing
from . import mrp_production

View File

@@ -1,41 +0,0 @@
from odoo import models, api
class MrpProduction(models.Model):
_inherit = 'mrp.production'
@api.model_create_multi
def create(self, vals_list):
"""
创建生产计划
"""
productions = super().create(vals_list)
for production in productions:
# 工单耗时
workorder_duration = 0
for workorder in production.workorder_ids:
workorder_duration += workorder.duration_expected
sale_order = self.env['sale.order'].sudo().search([('name', '=', production.origin)])
# 如果订单为空,则获取来源制造订单的销售单
if not sale_order:
mrp_production = self.env['mrp.production'].sudo().search([('name', '=', production.origin)],
limit=1)
if mrp_production:
sale_order = self.env['sale.order'].sudo().search([('name', '=', mrp_production.origin)])
else:
mrp_production = production
# if sale_order:
# sale_order.write({'schedule_status': 'to schedule'})
self.env['sf.production.plan'].sudo().with_company(production.company_id).create({
'name': production.name,
'order_deadline': sale_order.deadline_of_delivery,
'production_id': production.id,
'date_planned_start': production.date_planned_start,
'origin': mrp_production.origin,
'product_qty': production.product_qty,
'product_id': production.product_id.id,
'state': 'draft',
})
return productions

View File

@@ -40,7 +40,8 @@ class StockPicking(models.Model):
""" """
out_quality_checks = self.env['quality.check'].search( out_quality_checks = self.env['quality.check'].search(
[('picking_id', '=', self.id), ('test_type_id.name', '=', '出厂检验报告')]) [('picking_id', '=', self.id), ('test_type_id.name', '=', '出厂检验报告'),
('quality_state', '=', 'pass')])
# out_quality_checks 可能存在多个 # out_quality_checks 可能存在多个
if out_quality_checks: if out_quality_checks:
for out_quality_check in out_quality_checks: for out_quality_check in out_quality_checks: