# -*- coding: utf-8 -*- from odoo import models, fields, api, _ from odoo.exceptions import UserError class GarmentMrpRequirement(models.Model): _name = 'garment.mrp.requirement' _inherit = ['mail.thread', 'mail.activity.mixin'] _description = 'Garment Material Requirement Planning (MRP)' _order = 'name desc' name = fields.Char(string='MRP Requisition No', required=True, default=lambda self: _('New'), copy=False) production_plan_id = fields.Many2one('garment.production.plan', string='Production Plan', required=True, tracking=True) style_id = fields.Many2one('garment.style', string='Garment Style', related='production_plan_id.style_id', readonly=True) bom_id = fields.Many2one('garment.bom', string='Garment BOM', required=True) order_qty = fields.Integer(string='Production Order Qty (Pcs)', required=True, default=1000) company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company) line_ids = fields.One2many('garment.mrp.requirement.line', 'mrp_id', string='Material Shortage & Requirement Lines') total_shortage_cost = fields.Float(string='Total Shortage Purchase Cost', compute='_compute_totals', store=True) state = fields.Selection([ ('draft', 'Draft'), ('calculated', 'Requirements Calculated'), ('rfq_created', 'Purchase RFQs Generated'), ('closed', 'Completed / Closed'), ], string='Status', default='draft', tracking=True) purchase_order_ids = fields.Many2many('purchase.order', string='Generated Purchase Orders') purchase_count = fields.Integer(string='Purchase Orders', compute='_compute_purchase_count') @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('garment.mrp.requirement') or _('MRP/%s') % fields.Date.today().strftime('%Y%m%d') return super().create(vals_list) @api.depends('line_ids.shortage_cost') def _compute_totals(self): for mrp in self: mrp.total_shortage_cost = sum(mrp.line_ids.mapped('shortage_cost')) @api.depends('purchase_order_ids') def _compute_purchase_count(self): for mrp in self: mrp.purchase_count = len(mrp.purchase_order_ids) def action_compute_requirements(self): self.ensure_one() if not self.bom_id: raise UserError(_("Please assign a BOM.")) # Clear existing self.line_ids.unlink() calc = self.bom_id.calculate_requirement_for_qty(self.order_qty) lines = [] # Fabric Requirements for fab in self.bom_id.fabric_line_ids: factor = self.order_qty / (self.bom_id.base_qty or 1.0) req_kg = round(fab.gross_consumption_kg * factor, 2) product = fab.product_id avail_stock = product.qty_available if product else 0.0 shortage = max(0.0, req_kg - avail_stock) lines.append((0, 0, { 'material_type': 'fabric', 'name': f"{fab.fabric_type_id.name or 'Fabric'} ({fab.component_type.upper()})", 'product_id': product.id if product else False, 'required_qty': req_kg, 'uom_name': 'Kg', 'available_qty': avail_stock, 'shortage_qty': shortage, 'to_purchase_qty': shortage, 'unit_price': fab.cost_per_kg or 320.0, })) # Trims Requirements for trim in self.bom_id.trim_line_ids: factor = self.order_qty / (self.bom_id.base_qty or 1.0) req_qty = round(trim.gross_qty * factor, 2) product = trim.product_id avail_stock = product.qty_available if product else 0.0 shortage = max(0.0, req_qty - avail_stock) lines.append((0, 0, { 'material_type': 'trim', 'name': trim.item_name, 'product_id': product.id if product else False, 'required_qty': req_qty, 'uom_name': trim.uom_id.name if trim.uom_id else 'Units', 'available_qty': avail_stock, 'shortage_qty': shortage, 'to_purchase_qty': shortage, 'unit_price': trim.unit_cost or 1.5, })) self.write({ 'line_ids': lines, 'state': 'calculated', }) def action_generate_purchase_rfqs(self): """Generates draft Purchase Orders for all shortage materials""" self.ensure_one() shortage_lines = self.line_ids.filtered(lambda l: l.to_purchase_qty > 0) if not shortage_lines: raise UserError(_("No material shortages found to purchase.")) # Find or create a default vendor if product doesn't have one default_vendor = self.env['res.partner'].search([('supplier_rank', '>', 0)], limit=1) if not default_vendor: default_vendor = self.env['res.partner'].create({ 'name': 'Garment Raw Materials Supplier', 'supplier_rank': 1, }) po_lines = [] for line in shortage_lines: product = line.product_id if not product: product = self.env['product.product'].create({ 'name': line.name, 'type': 'consu', }) line.product_id = product.id po_lines.append((0, 0, { 'product_id': product.id, 'name': f"Requirement for {self.production_plan_id.name}: {line.name}", 'product_qty': line.to_purchase_qty, 'price_unit': line.unit_price, 'date_planned': fields.Datetime.now(), })) po = self.env['purchase.order'].create({ 'partner_id': default_vendor.id, 'origin': f"MRP: {self.name} / {self.production_plan_id.name}", 'order_line': po_lines, }) self.write({ 'purchase_order_ids': [(4, po.id)], 'state': 'rfq_created', }) return { 'name': _('Generated Purchase Order'), 'type': 'ir.actions.act_window', 'res_model': 'purchase.order', 'res_id': po.id, 'view_mode': 'form', } class GarmentMrpRequirementLine(models.Model): _name = 'garment.mrp.requirement.line' _description = 'Garment MRP Material Shortage Line' mrp_id = fields.Many2one('garment.mrp.requirement', string='MRP', required=True, ondelete='cascade') material_type = fields.Selection([ ('fabric', 'Fabric (Kg)'), ('trim', 'Trims & Accessories'), ('packaging', 'Packaging Material'), ], string='Type', default='fabric') name = fields.Char(string='Material Description', required=True) product_id = fields.Many2one('product.product', string='Inventory Product') uom_name = fields.Char(string='UoM', default='Kg') required_qty = fields.Float(string='Required Quantity', default=0.0) available_qty = fields.Float(string='Available Stock', default=0.0) shortage_qty = fields.Float(string='Stock Shortage', default=0.0) to_purchase_qty = fields.Float(string='To Purchase Qty', default=0.0) unit_price = fields.Float(string='Est. Unit Price', default=0.0) shortage_cost = fields.Float(string='Est. Shortage Cost', compute='_compute_shortage_cost', store=True) @api.depends('to_purchase_qty', 'unit_price') def _compute_shortage_cost(self): for line in self: line.shortage_cost = line.to_purchase_qty * line.unit_price