# -*- coding: utf-8 -*- from odoo import models, fields, api, _ class GarmentSewingLine(models.Model): _name = 'garment.sewing.line' _inherit = ['mail.thread', 'mail.activity.mixin'] _description = 'Garment Sewing Line' _order = 'name' name = fields.Char(string='Line Name', required=True) code = fields.Char(string='Line Code', required=True) supervisor_id = fields.Many2one('res.users', string='Line Supervisor', tracking=True) operator_count = fields.Integer(string='Standard Operators', default=20) helper_count = fields.Integer(string='Helpers / Feeders', default=4) daily_capacity_pcs = fields.Integer(string='Standard Daily Target (Pcs)', default=1000) active = fields.Boolean(default=True) notes = fields.Text(string='Machine Layout / Setup Notes') _code_uniq = models.Constraint('UNIQUE(code)', 'Line Code must be unique!') class GarmentSewingHourly(models.Model): _name = 'garment.sewing.hourly' _inherit = ['mail.thread', 'mail.activity.mixin'] _description = 'Sewing Floor Hourly Production Log' _order = 'date desc, line_id' name = fields.Char(string='Reference', compute='_compute_name', store=True) line_id = fields.Many2one('garment.sewing.line', string='Sewing Line', required=True, tracking=True) date = fields.Date(string='Production Date', default=fields.Date.today, required=True, tracking=True) shift = fields.Selection([('general', 'General Shift (08:30-17:30)'), ('shift_a', 'Shift A'), ('shift_b', 'Shift B')], string='Shift', default='general') style_id = fields.Many2one('garment.style', string='Garment Style Running', required=True, tracking=True) production_plan_id = fields.Many2one('garment.production.plan', string='Production Order', tracking=True) # Operators & SAM operators_present = fields.Integer(string='Operators Present', default=20, required=True) sam = fields.Float(string='Garment SAM (Minutes)', default=12.0, required=True) working_minutes_per_hour = fields.Integer(string='Minutes / Hour', default=60) # Hourly Outputs (Target, Actual, Rejections) h1_target = fields.Integer(string='H1 Target (08:30-09:30)', default=120) h1_actual = fields.Integer(string='H1 Actual', default=0) h1_defect = fields.Integer(string='H1 Defect', default=0) h2_target = fields.Integer(string='H2 Target (09:30-10:30)', default=120) h2_actual = fields.Integer(string='H2 Actual', default=0) h2_defect = fields.Integer(string='H2 Defect', default=0) h3_target = fields.Integer(string='H3 Target (10:30-11:30)', default=120) h3_actual = fields.Integer(string='H3 Actual', default=0) h3_defect = fields.Integer(string='H3 Defect', default=0) h4_target = fields.Integer(string='H4 Target (11:30-12:30)', default=120) h4_actual = fields.Integer(string='H4 Actual', default=0) h4_defect = fields.Integer(string='H4 Defect', default=0) h5_target = fields.Integer(string='H5 Target (13:15-14:15)', default=120) h5_actual = fields.Integer(string='H5 Actual', default=0) h5_defect = fields.Integer(string='H5 Defect', default=0) h6_target = fields.Integer(string='H6 Target (14:15-15:15)', default=120) h6_actual = fields.Integer(string='H6 Actual', default=0) h6_defect = fields.Integer(string='H6 Defect', default=0) h7_target = fields.Integer(string='H7 Target (15:15-16:15)', default=120) h7_actual = fields.Integer(string='H7 Actual', default=0) h7_defect = fields.Integer(string='H7 Defect', default=0) h8_target = fields.Integer(string='H8 Target (16:15-17:15)', default=120) h8_actual = fields.Integer(string='H8 Actual', default=0) h8_defect = fields.Integer(string='H8 Defect', default=0) ot_target = fields.Integer(string='OT Target (Overtime)', default=0) ot_actual = fields.Integer(string='OT Actual', default=0) ot_defect = fields.Integer(string='OT Defect', default=0) # Cumulative Day Totals total_target = fields.Integer(string='Total Day Target', compute='_compute_totals', store=True) total_actual = fields.Integer(string='Total Day Output', compute='_compute_totals', store=True) total_defects = fields.Integer(string='Total Alterations / Defects', compute='_compute_totals', store=True) total_worked_hours = fields.Float(string='Total Hours Worked', default=8.0) efficiency_pct = fields.Float(string='Line Efficiency %', compute='_compute_efficiency', store=True) efficiency_badge = fields.Selection([ ('excellent', 'Excellent (>=90%)'), ('good', 'Good (75%-89%)'), ('needs_attention', 'Below Target (<75%)'), ], string='Performance Rating', compute='_compute_efficiency', store=True) @api.depends('line_id', 'date') def _compute_name(self): for rec in self: line_name = rec.line_id.name if rec.line_id else 'Line' rec.name = f"{line_name} - {rec.date}" @api.onchange('line_id') def _onchange_line_id(self): if self.line_id: self.operators_present = self.line_id.operator_count @api.onchange('style_id') def _onchange_style_id(self): if self.style_id: self.sam = self.style_id.sam or 12.0 @api.depends('h1_target', 'h2_target', 'h3_target', 'h4_target', 'h5_target', 'h6_target', 'h7_target', 'h8_target', 'ot_target', 'h1_actual', 'h2_actual', 'h3_actual', 'h4_actual', 'h5_actual', 'h6_actual', 'h7_actual', 'h8_actual', 'ot_actual', 'h1_defect', 'h2_defect', 'h3_defect', 'h4_defect', 'h5_defect', 'h6_defect', 'h7_defect', 'h8_defect', 'ot_defect') def _compute_totals(self): for rec in self: rec.total_target = (rec.h1_target + rec.h2_target + rec.h3_target + rec.h4_target + rec.h5_target + rec.h6_target + rec.h7_target + rec.h8_target + rec.ot_target) rec.total_actual = (rec.h1_actual + rec.h2_actual + rec.h3_actual + rec.h4_actual + rec.h5_actual + rec.h6_actual + rec.h7_actual + rec.h8_actual + rec.ot_actual) rec.total_defects = (rec.h1_defect + rec.h2_defect + rec.h3_defect + rec.h4_defect + rec.h5_defect + rec.h6_defect + rec.h7_defect + rec.h8_defect + rec.ot_defect) @api.depends('total_actual', 'sam', 'operators_present', 'total_worked_hours', 'working_minutes_per_hour') def _compute_efficiency(self): for rec in self: total_minutes = (rec.operators_present or 1) * (rec.total_worked_hours or 8.0) * (rec.working_minutes_per_hour or 60) produced_minutes = (rec.total_actual or 0) * (rec.sam or 1.0) if total_minutes > 0: eff = round((produced_minutes / total_minutes) * 100.0, 1) else: eff = 0.0 rec.efficiency_pct = eff if eff >= 90.0: rec.efficiency_badge = 'excellent' elif eff >= 75.0: rec.efficiency_badge = 'good' else: rec.efficiency_badge = 'needs_attention'