176 lines
7.6 KiB
Python
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
|