# -*- coding: utf-8 -*- from odoo import models, fields, api, _ from odoo.exceptions import UserError class C2CRepackOrder(models.Model): _name = 'c2c.repack.order' _description = 'C2C Repacking Order' _order = 'create_date desc' name = fields.Char('Reference', default='New', readonly=True) state = fields.Selection([('draft', 'Draft'), ('done', 'Done')], default='draft', readonly=True) company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company) location_id = fields.Many2one('stock.location', string='Location', domain="[('usage', '=', 'internal')]", required=True) # Source (Bulk) source_product_id = fields.Many2one('product.product', string='Bulk Product', required=True) source_qty = fields.Float('Bulk Qty to Repack', required=True, default=1.0) source_uom_id = fields.Many2one('uom.uom', string='Bulk UoM', related='source_product_id.uom_id') source_lot_id = fields.Many2one('stock.lot', string='Bulk Lot', domain="[('product_id', '=', source_product_id)]") # Destination (Retail) dest_product_id = fields.Many2one('product.product', string='Retail Pack Product', required=True) dest_qty = fields.Float('Number of Retail Packs', required=True) dest_uom_id = fields.Many2one('uom.uom', string='Retail UoM', related='dest_product_id.uom_id') dest_lot_id = fields.Many2one('stock.lot', string='New Retail Lot', domain="[('product_id', '=', dest_product_id)]") move_ids = fields.One2many('stock.move', 'repack_id', string='Stock Moves') @api.model_create_multi def create(self, vals_list): for vals in vals_list: if vals.get('name', 'New') == 'New': vals['name'] = self.env['ir.sequence'].next_by_code('c2c.repack.order') or 'REPACK' return super().create(vals_list) def action_confirm_repack(self): """Consume bulk and create retail packs.""" self.ensure_one() if self.state == 'done': return if self.source_qty <= 0 or self.dest_qty <= 0: raise UserError('Quantities must be strictly positive.') prod_location = self.location_id scrap_location = self.env['stock.location'].search([('scrap_location', '=', True), ('company_id', '=', self.company_id.id)], limit=1) if not scrap_location: # Fallback to standard production loction usually handled via standard routes, but we can do a simple virtual production loc scrap_location = self.env.ref('stock.location_production') # 1. Consume Bulk (Move from Internal to Production) consume_move = self.env['stock.move'].create({ 'name': 'Repack Consume %s' % self.name, 'product_id': self.source_product_id.id, 'product_uom_qty': self.source_qty, 'product_uom': self.source_uom_id.id, 'location_id': prod_location.id, 'location_dest_id': scrap_location.id, 'repack_id': self.id, 'state': 'draft', 'company_id': self.company_id.id, }) consume_move._action_confirm() if self.source_lot_id: self.env['stock.move.line'].create({ 'move_id': consume_move.id, 'product_id': self.source_product_id.id, 'product_uom_id': self.source_uom_id.id, 'quantity': self.source_qty, 'lot_id': self.source_lot_id.id, 'location_id': prod_location.id, 'location_dest_id': scrap_location.id, }) else: consume_move.quantity = self.source_qty consume_move._action_done() # 2. Produce Retail Packs (Move from Production to Internal) produce_move = self.env['stock.move'].create({ 'name': 'Repack Produce %s' % self.name, 'product_id': self.dest_product_id.id, 'product_uom_qty': self.dest_qty, 'product_uom': self.dest_uom_id.id, 'location_id': scrap_location.id, 'location_dest_id': prod_location.id, 'repack_id': self.id, 'state': 'draft', 'company_id': self.company_id.id, }) produce_move._action_confirm() if self.dest_lot_id: self.env['stock.move.line'].create({ 'move_id': produce_move.id, 'product_id': self.dest_product_id.id, 'product_uom_id': self.dest_uom_id.id, 'quantity': self.dest_qty, 'lot_id': self.dest_lot_id.id, 'location_id': scrap_location.id, 'location_dest_id': prod_location.id, }) else: produce_move.quantity = self.dest_qty produce_move._action_done() self.write({'state': 'done'}) class StockMove(models.Model): _inherit = 'stock.move' repack_id = fields.Many2one('c2c.repack.order', string='Repack Order', copy=False)