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