Garment_ERP/addons/garment_erp/models/mrp_requirement.py
2026-09-21 16:43:23 +05:30

176 lines
7.6 KiB
Python

# -*- 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