101 lines
5.9 KiB
Python
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
|