1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174
|
# -*- 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
|