2026-09-21 16:43:23 +05:30

101 lines
5.9 KiB
Python

# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class GarmentCosting(models.Model):
_name = 'garment.costing'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Garment Pre-Costing & Post-Costing Sheet'
_order = 'name desc'
name = fields.Char(string='Cost Sheet No', required=True, default=lambda self: _('New'), copy=False, tracking=True)
style_id = fields.Many2one('garment.style', string='Garment Style', required=True, tracking=True)
buyer_id = fields.Many2one('res.partner', string='Buyer / Customer', tracking=True)
order_qty = fields.Integer(string='Target Order Quantity (Pcs)', required=True, default=10000)
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
currency_id = fields.Many2one('res.currency', string='Currency', default=lambda self: self.env.company.currency_id)
costing_type = fields.Selection([
('pre_costing', 'Pre-Costing (Quotation / Estimate)'),
('post_costing', 'Post-Costing (Actual Production Variance)'),
], string='Costing Stage', default='pre_costing', required=True, tracking=True)
# 1. Raw Materials (Fabric + Dyeing)
fabric_cost_per_pc = fields.Float(string='Yarn / Raw Fabric Cost / Pc', default=65.0, digits=(16, 2))
dyeing_finishing_cost_per_pc = fields.Float(string='Dyeing & Bleaching / Pc', default=12.0, digits=(16, 2))
# 2. Trims & Accessories
trims_accessories_cost_per_pc = fields.Float(string='Trims & Labels / Pc', default=8.5, digits=(16, 2))
# 3. Value Addition
printing_embroidery_cost_per_pc = fields.Float(string='Chest Print / Embroidery / Pc', default=14.0, digits=(16, 2))
washing_cost_per_pc = fields.Float(string='Garment Bio-Washing / Pc', default=5.0, digits=(16, 2))
# 4. Manufacturing Labor (CM - Cut & Make)
cutting_cost_per_pc = fields.Float(string='Cutting Labor / Pc', default=4.0, digits=(16, 2))
sewing_cm_cost_per_pc = fields.Float(string='Sewing Assembly CM / Pc', default=28.0, digits=(16, 2), help='Standard sewing labor cost')
# 5. Packaging & Forwarding
packing_carton_cost_per_pc = fields.Float(string='Polybags & Master Cartons / Pc', default=6.5, digits=(16, 2))
commercial_freight_cost_per_pc = fields.Float(string='Local Freight & Port Handling / Pc', default=3.0, digits=(16, 2))
# 6. Factory Overheads
factory_overhead_pct = fields.Float(string='Factory Overheads %', default=8.0)
factory_overhead_cost = fields.Float(string='Overheads Cost / Pc', compute='_compute_totals', store=True)
# Total Costs
total_cost_per_pc = fields.Float(string='Total Garment Cost / Pc', compute='_compute_totals', store=True)
total_cost_per_dozen = fields.Float(string='Total Cost / Dozen', compute='_compute_totals', store=True)
total_order_cost = fields.Float(string='Total Order Cost', compute='_compute_totals', store=True)
# Commercial Pricing & Margins
target_selling_price = fields.Float(string='Target Selling Price / Pc', default=180.0, required=True, tracking=True)
gross_margin_amount = fields.Float(string='Gross Margin / Pc', compute='_compute_margins', store=True)
gross_margin_pct = fields.Float(string='Gross Margin %', compute='_compute_margins', store=True)
total_expected_profit = fields.Float(string='Total Expected Profit', compute='_compute_margins', store=True)
notes = fields.Text(string='Costing Notes & Assumptions')
@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.costing') or _('COST/%s') % fields.Date.today().strftime('%Y%m%d')
return super(GarmentCosting, self).create(vals_list)
@api.depends('fabric_cost_per_pc', 'dyeing_finishing_cost_per_pc', 'trims_accessories_cost_per_pc',
'printing_embroidery_cost_per_pc', 'washing_cost_per_pc', 'cutting_cost_per_pc',
'sewing_cm_cost_per_pc', 'packing_carton_cost_per_pc', 'commercial_freight_cost_per_pc',
'factory_overhead_pct', 'order_qty')
def _compute_totals(self):
for rec in self:
direct_cost = (rec.fabric_cost_per_pc + rec.dyeing_finishing_cost_per_pc +
rec.trims_accessories_cost_per_pc + rec.printing_embroidery_cost_per_pc +
rec.washing_cost_per_pc + rec.cutting_cost_per_pc +
rec.sewing_cm_cost_per_pc + rec.packing_carton_cost_per_pc +
rec.commercial_freight_cost_per_pc)
rec.factory_overhead_cost = round(direct_cost * (rec.factory_overhead_pct / 100.0), 2)
tot_pc = round(direct_cost + rec.factory_overhead_cost, 2)
rec.total_cost_per_pc = tot_pc
rec.total_cost_per_dozen = round(tot_pc * 12.0, 2)
rec.total_order_cost = round(tot_pc * (rec.order_qty or 1), 2)
@api.depends('total_cost_per_pc', 'target_selling_price', 'order_qty')
def _compute_margins(self):
for rec in self:
margin = rec.target_selling_price - rec.total_cost_per_pc
rec.gross_margin_amount = round(margin, 2)
if rec.target_selling_price > 0:
rec.gross_margin_pct = round((margin / rec.target_selling_price) * 100.0, 2)
else:
rec.gross_margin_pct = 0.0
rec.total_expected_profit = round(margin * (rec.order_qty or 1), 2)
def action_load_from_bom(self):
"""Pre-populates raw material and labor costs from the active Garment BOM"""
self.ensure_one()
bom = self.env['garment.bom'].search([('style_id', '=', self.style_id.id)], limit=1)
if bom:
self.fabric_cost_per_pc = bom.total_fabric_cost
self.trims_accessories_cost_per_pc = bom.total_trim_cost
self.sewing_cm_cost_per_pc = bom.total_operation_cost