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

137 lines
7.2 KiB
Python

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