# -*- coding: utf-8 -*- from odoo import models, fields, api, _ from odoo.exceptions import UserError class GarmentJobworkOrder(models.Model): _name = 'garment.jobwork.order' _inherit = ['mail.thread', 'mail.activity.mixin'] _description = 'Garment Subcontracting & Job Work Order' _order = 'name desc' name = fields.Char(string='Job Work DC No', required=True, default=lambda self: _('New'), copy=False, tracking=True) vendor_id = fields.Many2one('res.partner', string='Job Worker / Subcontractor', required=True, domain=[('supplier_rank', '>', 0)], tracking=True) production_plan_id = fields.Many2one('garment.production.plan', string='Production Plan', tracking=True) style_id = fields.Many2one('garment.style', string='Garment Style', tracking=True) company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company) process_type = fields.Selection([ ('knitting', 'Knitting / Greige Fabric Formation'), ('dyeing', 'Dyeing & Bleaching / Bio-Polishing'), ('all_over_print', 'All-Over Rotary / Mill Print'), ('chest_print', 'Chest Print (Screen / DTF / Sublimation)'), ('embroidery', 'Computerized Multi-Head Embroidery'), ('washing', 'Garment Washing / Enzyme / Silicone Wash'), ('finishing', 'External Ironing & Thread Trimming'), ('other', 'Other Job Work'), ], string='Processing Operation', default='dyeing', required=True, tracking=True) # Dates & Challan date_sent = fields.Date(string='DC Issue Date', default=fields.Date.today, tracking=True) date_expected = fields.Date(string='Expected Inward Date', tracking=True) date_received = fields.Date(string='Final Completion Date') vehicle_no = fields.Char(string='Vehicle / Transport No') e_way_bill_no = fields.Char(string='GST E-Way Bill Number') # Financials & Process Loss pricing_uom = fields.Selection([('per_kg', 'Per Kilogram (Kg)'), ('per_pc', 'Per Piece (Pc)')], string='Rate Basis', default='per_kg') rate_per_unit = fields.Float(string='Job Work Rate (INR)', default=45.0, required=True) allowed_process_loss_pct = fields.Float(string='Standard Process Loss %', default=4.0, help='Allowed shrinkage/wastage during process') # Lines sent_line_ids = fields.One2many('garment.jobwork.sent.line', 'jobwork_id', string='Materials Sent Outward') inward_line_ids = fields.One2many('garment.jobwork.inward.line', 'jobwork_id', string='Materials Inward Received') # Computes total_sent_qty = fields.Float(string='Total Sent Qty', compute='_compute_quantities', store=True) total_received_qty = fields.Float(string='Total Received Qty', compute='_compute_quantities', store=True) total_rejected_qty = fields.Float(string='Rejected Qty', compute='_compute_quantities', store=True) total_accepted_qty = fields.Float(string='Accepted Inward Qty', compute='_compute_quantities', store=True) pending_qty = fields.Float(string='Pending Balance Qty', compute='_compute_quantities', store=True) actual_process_loss_pct = fields.Float(string='Actual Process Loss %', compute='_compute_process_loss', store=True) total_cost = fields.Float(string='Total Job Work Cost', compute='_compute_cost', store=True) state = fields.Selection([ ('draft', 'Draft DC'), ('sent', 'Material Sent (Outward)'), ('partial', 'Partially Received'), ('completed', 'Completed & Inwarded'), ('cancelled', 'Cancelled'), ], string='Status', default='draft', tracking=True, required=True) notes = fields.Text(string='Processing Instructions / Shade Formula') @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.jobwork.order') or _('JW/%s') % fields.Date.today().strftime('%Y%m%d') return super(GarmentJobworkOrder, self).create(vals_list) @api.depends('sent_line_ids.quantity', 'inward_line_ids.received_qty', 'inward_line_ids.rejected_qty', 'inward_line_ids.accepted_qty') def _compute_quantities(self): for jw in self: sent = sum(jw.sent_line_ids.mapped('quantity')) rec = sum(jw.inward_line_ids.mapped('received_qty')) rej = sum(jw.inward_line_ids.mapped('rejected_qty')) acc = sum(jw.inward_line_ids.mapped('accepted_qty')) jw.total_sent_qty = sent jw.total_received_qty = rec jw.total_rejected_qty = rej jw.total_accepted_qty = acc jw.pending_qty = max(0.0, round(sent - rec, 2)) @api.depends('total_sent_qty', 'total_received_qty') def _compute_process_loss(self): for jw in self: if jw.total_sent_qty > 0 and jw.total_received_qty > 0: diff = jw.total_sent_qty - jw.total_received_qty jw.actual_process_loss_pct = max(0.0, round((diff / jw.total_sent_qty) * 100.0, 2)) else: jw.actual_process_loss_pct = 0.0 @api.depends('total_accepted_qty', 'rate_per_unit') def _compute_cost(self): for jw in self: jw.total_cost = round(jw.total_accepted_qty * jw.rate_per_unit, 2) def action_dispatch_material(self): self.ensure_one() if not self.sent_line_ids: raise UserError(_("Please add materials sent outward before dispatching.")) self.write({'state': 'sent'}) def action_complete_jobwork(self): self.ensure_one() self.write({'state': 'completed', 'date_received': fields.Date.today()}) class GarmentJobworkSentLine(models.Model): _name = 'garment.jobwork.sent.line' _description = 'Job Work Sent Line' jobwork_id = fields.Many2one('garment.jobwork.order', string='Job Work Order', required=True, ondelete='cascade') description = fields.Char(string='Item / Material Description', required=True) fabric_roll_id = fields.Many2one('garment.fabric.roll', string='Fabric Roll #') bundle_id = fields.Many2one('garment.bundle', string='Garment Bundle #') color_id = fields.Many2one('garment.color', string='Color') quantity = fields.Float(string='Sent Quantity', required=True, default=0.0) uom = fields.Char(string='UoM', default='Kg') class GarmentJobworkInwardLine(models.Model): _name = 'garment.jobwork.inward.line' _description = 'Job Work Inward Received Line' jobwork_id = fields.Many2one('garment.jobwork.order', string='Job Work Order', required=True, ondelete='cascade') date_inward = fields.Date(string='Inward Date', default=fields.Date.today, required=True) vendor_dc_no = fields.Char(string='Vendor Delivery Challan #', required=True) received_qty = fields.Float(string='Received Qty', required=True, default=0.0) rejected_qty = fields.Float(string='Rejected Qty', default=0.0) accepted_qty = fields.Float(string='Accepted Good Qty', compute='_compute_accepted', store=True) remarks = fields.Char(string='Shade / Quality Remarks') @api.depends('received_qty', 'rejected_qty') def _compute_accepted(self): for line in self: line.accepted_qty = max(0.0, line.received_qty - line.rejected_qty)