Files
test/sf_manufacturing_orders/models/sf_production.py
2022-11-08 10:39:45 +08:00

317 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
import logging
from collections import defaultdict, namedtuple
from odoo.addons.stock.models.stock_rule import ProcurementException
from dateutil.relativedelta import relativedelta
from odoo import SUPERUSER_ID, _, api, fields, models, registry
from odoo.exceptions import UserError
from odoo.osv import expression
from odoo.tools import float_compare, float_is_zero, html_escape
from odoo.tools.misc import split_every
_logger = logging.getLogger(__name__)
class ProductTemplate(models.Model):
_inherit = 'product.template'
_description = '产品模板'
single_manufacturing = fields.Boolean(string="单个制造")
class ProcurementGroup(models.Model):
_inherit = 'procurement.group'
class MrpProduction(models.Model):
_inherit = 'mrp.production'
_description = "制造订单"
# 重载根据工序生成工单的程序如果产品BOM中没有工序时
# 根据产品对应的模板类型中工序,去生成工单;
# CNC加工工序的选取规则
# 如果自动报价有带过来预分配的机床,
# 则根据设备找到工作中心;否则采用前面描述的工作中心分配机制;
# 其他规则限制: 默认只分配给工作中心状态为非故障的工作中心;
def _create_workorder(self):
for production in self:
if not production.bom_id or not production.product_id:
continue
workorders_values = []
product_qty = production.product_uom_id._compute_quantity(production.product_qty,
production.bom_id.product_uom_id)
exploded_boms, dummy = production.bom_id.explode(production.product_id,
product_qty / production.bom_id.product_qty,
picking_type=production.bom_id.picking_type_id)
for bom, bom_data in exploded_boms:
# If the operations of the parent BoM and phantom BoM are the same, don't recreate work orders.
if not (bom.operation_ids and (not bom_data['parent_line'] or bom_data[
'parent_line'].bom_id.operation_ids != bom.operation_ids)):
continue
for operation in bom.operation_ids:
if operation._skip_operation_line(bom_data['product']):
continue
workorders_values += [{
'name': operation.name,
'production_id': production.id,
'workcenter_id': operation.workcenter_id.id,
'product_uom_id': production.product_uom_id.id,
'operation_id': operation.id,
'state': 'pending',
}]
# 根据加工面板的面数及对应的工序模板生成工单
for k in (production.product_id.processing_panel.split(',')):
for route in production.product_id.model_type_id.routing_tmpl_ids:
if route:
workorders_values_str = [0, '', {
'product_uom_id': production.product_uom_id.id,
'qty_producing': 0,
'operation_id': False,
'name': route.route_workcenter_id.name,
'processing_panel': k,
'routing_type': route.routing_type,
'workcenter_id': self.env['mrp.routing.workcenter'].get_workcenter(route.workcenter_ids.ids),
'date_planned_start': False,
'date_planned_finished': False,
'duration_expected': 60,
'duration': 0
}]
workorders_values.append(workorders_values_str)
production.workorder_ids = workorders_values
for workorder in production.workorder_ids:
workorder.duration_expected = workorder._get_duration_expected()
class StockRule(models.Model):
_inherit = 'stock.rule'
@api.model
def _run_pull(self, procurements):
moves_values_by_company = defaultdict(list)
mtso_products_by_locations = defaultdict(list)
# To handle the `mts_else_mto` procure method, we do a preliminary loop to
# isolate the products we would need to read the forecasted quantity,
# in order to to batch the read. We also make a sanitary check on the
# `location_src_id` field.
# list1 = []
# for item in procurements:
# num = int(item[0].product_qty)
# if num > 1:
# for no in range(1, num+1):
#
# Procurement = namedtuple('Procurement', ['product_id', 'product_qty',
# 'product_uom', 'location_id', 'name', 'origin',
# 'company_id',
# 'values'])
# s = Procurement(product_id=item[0].product_id,product_qty=1.0,product_uom=item[0].product_uom,
# location_id=item[0].location_id,
# name=item[0].name,
# origin=item[0].origin,
# company_id=item[0].company_id,
# values=item[0].values,
# )
# item1 = list(item)
# item1[0]=s
#
# list1.append(tuple(item1))
# else:
# list1.append(item)
for procurement, rule in procurements:
if not rule.location_src_id:
msg = _('No source location defined on stock rule: %s!') % (rule.name,)
raise ProcurementException([(procurement, msg)])
if rule.procure_method == 'mts_else_mto':
mtso_products_by_locations[rule.location_src_id].append(procurement.product_id.id)
# Get the forecasted quantity for the `mts_else_mto` procurement.
forecasted_qties_by_loc = {}
for location, product_ids in mtso_products_by_locations.items():
products = self.env['product.product'].browse(product_ids).with_context(location=location.id)
forecasted_qties_by_loc[location] = {product.id: product.free_qty for product in products}
# Prepare the move values, adapt the `procure_method` if needed.
procurements = sorted(procurements, key=lambda proc: float_compare(proc[0].product_qty, 0.0,
precision_rounding=proc[
0].product_uom.rounding) > 0)
list2 = []
for item in procurements:
num = int(item[0].product_qty)
product = self.env['product.template'].search(
["&", ("name", '=', item[0].product_id.display_name), ('single_manufacturing', '!=', False)])
if product:
if num > 1:
for no in range(1, num + 1):
Procurement = namedtuple('Procurement', ['product_id', 'product_qty',
'product_uom', 'location_id', 'name', 'origin',
'company_id',
'values'])
s = Procurement(product_id=item[0].product_id, product_qty=1.0, product_uom=item[0].product_uom,
location_id=item[0].location_id,
name=item[0].name,
origin=item[0].origin,
company_id=item[0].company_id,
values=item[0].values,
)
item1 = list(item)
item1[0] = s
list2.append(tuple(item1))
else:
list2.append(item)
else:
list2.append(item)
for procurement, rule in list2:
procure_method = rule.procure_method
if rule.procure_method == 'mts_else_mto':
qty_needed = procurement.product_uom._compute_quantity(procurement.product_qty,
procurement.product_id.uom_id)
if float_compare(qty_needed, 0, precision_rounding=procurement.product_id.uom_id.rounding) <= 0:
procure_method = 'make_to_order'
for move in procurement.values.get('group_id', self.env['procurement.group']).stock_move_ids:
if move.rule_id == rule and float_compare(move.product_uom_qty, 0,
precision_rounding=move.product_uom.rounding) > 0:
procure_method = move.procure_method
break
forecasted_qties_by_loc[rule.location_src_id][procurement.product_id.id] -= qty_needed
elif float_compare(qty_needed, forecasted_qties_by_loc[rule.location_src_id][procurement.product_id.id],
precision_rounding=procurement.product_id.uom_id.rounding) > 0:
procure_method = 'make_to_order'
else:
forecasted_qties_by_loc[rule.location_src_id][procurement.product_id.id] -= qty_needed
procure_method = 'make_to_stock'
move_values = rule._get_stock_move_values(*procurement)
move_values['procure_method'] = procure_method
moves_values_by_company[procurement.company_id.id].append(move_values)
for company_id, moves_values in moves_values_by_company.items():
# create the move as SUPERUSER because the current user may not have the rights to do it (mto product launched by a sale for example)
moves = self.env['stock.move'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(moves_values)
# Since action_confirm launch following procurement_group we should activate it.
moves._action_confirm()
return True
# @api.model
# def _run_manufacture(self, procurements):
# productions_values_by_company = defaultdict(list)
# errors = []
# for procurement, rule in procurements:
# 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.
# continue
# bom = rule._get_matching_bom(procurement.product_id, procurement.company_id, procurement.values)
#
# product = self.env['product.template'].search(
# ["&", ("id", '=', procurement.product_id.id), ('single_manufacturing', '!=', False)])
# if not product:
# productions_values_by_company[procurement.company_id.id].append(
# rule._prepare_mo_vals1(*procurement, bom))
# else:
# orders_vals = rule._prepare_mo_vals(*procurement, bom)
#
# if isinstance(orders_vals, list):
# for vals in orders_vals:
# productions_values_by_company[procurement.company_id.id].append(vals)
# else:
# productions_values_by_company[procurement.company_id.id].append(orders_vals)
#
# if errors:
# raise ProcurementException(errors)
#
# 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 (mto product launched by a sale for example)
# productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(
# productions_values)
# a = self.env['stock.move'].sudo().create(productions._get_moves_raw_values())
# b = self.env['stock.move'].sudo().create(productions._get_moves_finished_values())
# c = productions._create_workorder()
# 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_raw_ids and not p.workorder_ids)).action_confirm()
#
# for production in productions:
# origin_production = production.move_dest_ids and production.move_dest_ids[
# 0].raw_material_production_id or False
# orderpoint = production.orderpoint_id
# if orderpoint and orderpoint.create_uid.id == SUPERUSER_ID and orderpoint.trigger == 'manual':
# production.message_post(
# body=_('This production order has been created from Replenishment Report.'),
# message_type='comment',
# subtype_xmlid='mail.mt_note')
# elif orderpoint:
# production.message_post_with_view(
# 'mail.message_origin_link',
# values={'self': production, 'origin': orderpoint},
# subtype_id=self.env.ref('mail.mt_note').id)
# elif origin_production:
# production.message_post_with_view(
# 'mail.message_origin_link',
# values={'self': production, 'origin': origin_production},
# subtype_id=self.env.ref('mail.mt_note').id)
# return True
#
# def _prepare_mo_vals1(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values,
# bom):
# date_planned = self._get_date_planned(product_id, company_id, values)
# date_deadline = values.get('date_deadline') or date_planned + relativedelta(
# days=company_id.manufacturing_lead) + relativedelta(days=product_id.produce_delay)
# mo_values = {
# 'origin': origin,
# 'product_id': product_id.id,
# 'product_description_variants': values.get('product_description_variants'),
# 'product_qty': product_qty,
# 'product_uom_id': product_uom.id,
# 'location_src_id': self.location_src_id.id or self.picking_type_id.default_location_src_id.id or location_id.id,
# 'location_dest_id': location_id.id,
# 'bom_id': bom.id,
# 'date_deadline': date_deadline,
# 'date_planned_start': date_planned,
# 'date_planned_finished': fields.Datetime.from_string(values['date_planned']),
# 'procurement_group_id': False,
# 'propagate_cancel': self.propagate_cancel,
# 'orderpoint_id': values.get('orderpoint_id', False) and values.get('orderpoint_id').id,
# 'picking_type_id': self.picking_type_id.id or values['warehouse_id'].manu_type_id.id,
# 'company_id': company_id.id,
# 'move_dest_ids': values.get('move_dest_ids') and [(4, x.id) for x in values['move_dest_ids']] or False,
# 'user_id': False,
# }
# # Use the procurement group created in _run_pull mrp override
# # Preserve the origin from the original stock move, if available
# if location_id.warehouse_id.manufacture_steps == 'pbm_sam' and values.get('move_dest_ids') and values.get(
# 'group_id') and values['move_dest_ids'][0].origin != values['group_id'].name:
# origin = values['move_dest_ids'][0].origin
# mo_values.update({
# 'name': values['group_id'].name,
# 'procurement_group_id': values['group_id'].id,
# 'origin': origin,
# })
# return mo_values
#
# def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values,
# bom):
#
# mo_lists = []
# for num in range(1, int(product_qty) + 1):
# vals = super(StockRule, self)._prepare_mo_vals(product_id, 1.0, product_uom, location_id, name, origin,
# company_id, values,
# bom)
# vals.update({
# # 'name': values['group_id'].name,
# 'procurement_group_id': values['group_id'].id,
# # 'origin': origin,
# })
# mo_lists.append(vals)
#
# return mo_lists