135 lines
7.0 KiB
Python
135 lines
7.0 KiB
Python
# -*- 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'
|