175 lines
7.6 KiB
Python
175 lines
7.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import re
|
|
|
|
from collections import defaultdict, deque
|
|
|
|
from odoo import _, api, fields, models
|
|
from odoo.exceptions import UserError
|
|
from odoo.tools import OrderedSet
|
|
|
|
|
|
class MrpBatchProduct(models.TransientModel):
|
|
_name = 'mrp.batch.produce'
|
|
_description = 'Produce a batch of production order'
|
|
|
|
production_id = fields.Many2one('mrp.production', 'Production')
|
|
|
|
production_text_help = fields.Text('Explanation for batch production', compute='_compute_production_text_help')
|
|
production_text = fields.Text('Batch Production')
|
|
|
|
lot_name = fields.Char('First Lot/SN', compute="_compute_lot_name", store=True, readonly=False)
|
|
lot_qty = fields.Integer('Number of SN', compute="_compute_lot_qty", store=True, readonly=False)
|
|
|
|
component_separator = fields.Char('Component separator', default=',', required=True)
|
|
lots_separator = fields.Char('Lot separator', default='|', required=True)
|
|
lots_quantity_separator = fields.Char('Lot quantity separator', default=';', required=True)
|
|
|
|
@api.depends('production_id')
|
|
def _compute_lot_name(self):
|
|
for wizard in self:
|
|
if wizard.lot_name:
|
|
continue
|
|
wizard.lot_name = self.production_id.lot_producing_id.name
|
|
if not wizard.lot_name:
|
|
wizard.lot_name = self.env['stock.lot']._get_next_serial(self.production_id.company_id, self.production_id.product_id)
|
|
|
|
@api.depends('production_id')
|
|
def _compute_lot_qty(self):
|
|
for wizard in self:
|
|
wizard.lot_qty = wizard.production_id.product_qty
|
|
|
|
@api.depends('production_id', 'component_separator')
|
|
def _compute_production_text_help(self):
|
|
basic_text = _("Write one line per finished product to produce, with serial numbers as follows:\n")
|
|
for wizard in self:
|
|
finished_product = wizard.production_id.product_id.display_name
|
|
text = basic_text + finished_product
|
|
for move_raw in wizard.production_id.move_raw_ids:
|
|
if move_raw.product_id.tracking == "none":
|
|
continue
|
|
text += wizard.component_separator + move_raw.product_id.display_name
|
|
wizard.production_text_help = text
|
|
|
|
def action_prepare(self):
|
|
return self._production_text_to_object(mark_done=False)
|
|
|
|
def action_done(self):
|
|
return self._production_text_to_object(mark_done=True)
|
|
|
|
def action_generate_production_text(self):
|
|
self.ensure_one()
|
|
if not self.lot_name:
|
|
raise UserError(_('Please specify the first serial number you would like to use.'))
|
|
lots_name = self.env['stock.lot'].generate_lot_names(self.lot_name, self.lot_qty)
|
|
self.production_text = '\n'.join([lot['lot_name'] for lot in lots_name])
|
|
action = self.env["ir.actions.actions"]._for_xml_id("mrp.action_mrp_batch_produce")
|
|
action['res_id'] = self.id
|
|
return action
|
|
|
|
def _production_text_to_object(self, mark_done=False):
|
|
self.ensure_one()
|
|
if not self.production_text:
|
|
raise UserError(_('Please specify the serial number you would like to use.'))
|
|
productions_amount = []
|
|
productions_lot_list = []
|
|
components_list = []
|
|
for production_line in deque(self.production_text.split("\n")):
|
|
production_line = production_line.strip()
|
|
if not production_line:
|
|
continue
|
|
components_line = deque(production_line.split(self.component_separator))
|
|
finished_line = components_line.popleft()
|
|
finished_move = self.production_id.move_finished_ids.filtered(lambda m: m.product_id == self.production_id.product_id)
|
|
finished_lot, finished_qty = self._get_lot_and_qty(finished_move, finished_line)
|
|
productions_amount.append(finished_qty)
|
|
productions_lot_list.append(finished_lot)
|
|
components_list.append(components_line)
|
|
|
|
productions = self.production_id._split_productions({self.production_id: productions_amount})
|
|
lots = self.env['stock.lot'].search(
|
|
domain=[
|
|
('company_id', 'in', [self.production_id.product_id.company_id.id, False]),
|
|
('name', 'in', productions_lot_list),
|
|
('product_id', '=', self.production_id.product_id.id)
|
|
]
|
|
)
|
|
existing_lot_names = lots.mapped('name')
|
|
raw_lots = []
|
|
for lot_name in productions_lot_list:
|
|
if lot_name in existing_lot_names:
|
|
continue
|
|
raw_lots.append({
|
|
'name': lot_name,
|
|
'product_id': productions.product_id.id
|
|
})
|
|
lots = lots + self.env['stock.lot'].create(raw_lots)
|
|
|
|
productions_to_set = OrderedSet()
|
|
for production, finished_lot in zip(productions, lots):
|
|
production.lot_producing_id = finished_lot
|
|
self._process_components(production, components_list.pop(0))
|
|
productions_to_set.add(production.id)
|
|
|
|
productions = self.env['mrp.production'].browse(productions_to_set)
|
|
for production in reversed(productions):
|
|
production.qty_producing = production.product_uom_qty
|
|
production.set_qty_producing()
|
|
production.move_raw_ids.picked = True
|
|
|
|
if mark_done:
|
|
return productions.with_context(from_wizard=True).button_mark_done()
|
|
return
|
|
|
|
def _process_components(self, production, components_line):
|
|
lot_names = []
|
|
mls_to_unlink = set()
|
|
moves_vals = defaultdict(list)
|
|
for move_raw in production.move_raw_ids:
|
|
if move_raw.product_id.tracking == "none" or not components_line:
|
|
continue
|
|
component_line = components_line.popleft().strip()
|
|
mls_lines = component_line.split(self.lots_separator)
|
|
for ml_line in mls_lines:
|
|
lot_name, qty = self._get_lot_and_qty(move_raw, ml_line)
|
|
moves_vals[move_raw].append((qty, lot_name))
|
|
lot_names.append(lot_name)
|
|
|
|
lots = {(l.name, l.product_id): l for l in self.env['stock.lot'].search([('name', 'in', lot_names)])}
|
|
mls_vals = []
|
|
for move, mls in moves_vals.items():
|
|
if mls:
|
|
mls_to_unlink |= set(move.move_line_ids.ids)
|
|
for qty, lot_name in mls:
|
|
ml_vals = self._prepapre_move_line_vals(move, qty, lot_name, lots)
|
|
mls_vals.append(ml_vals)
|
|
self.env['stock.move.line'].browse(mls_to_unlink).unlink()
|
|
self.env['stock.move.line'].create(mls_vals)
|
|
|
|
def _prepapre_move_line_vals(self, move, qty, lot_name, lots):
|
|
ml_vals = move._prepare_move_line_vals(qty)
|
|
lot = lots.get((lot_name, move.product_id))
|
|
if not lot:
|
|
if not move.picking_type_id.use_create_components_lots:
|
|
raise UserError(_('Lot %s does not exist.', lot_name))
|
|
lot = self.env['stock.lot'].create({
|
|
'name': lot_name,
|
|
'product_id': move.product_id.id
|
|
})
|
|
lots[(lot_name, move.product_id)] = lot
|
|
ml_vals['lot_id'] = lots[(lot_name, move.product_id)].id
|
|
ml_vals['picked'] = True
|
|
return ml_vals
|
|
|
|
def _get_lot_and_qty(self, move, text):
|
|
if self.lots_quantity_separator in text:
|
|
lot_name, qty = re.match(r'(.+)%s(.+)' % (self.lots_quantity_separator), text).groups()
|
|
return lot_name, float(qty)
|
|
elif move.product_id.tracking == "none":
|
|
return False, float(text)
|
|
elif move.product_id.tracking == "serial":
|
|
return text, 1
|
|
else:
|
|
return text, move.product_uom_qty
|