173 lines
8.5 KiB
Python
173 lines
8.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
from odoo import models, fields, api, _
|
|
from odoo.exceptions import UserError, ValidationError
|
|
|
|
class GarmentStyle(models.Model):
|
|
_name = 'garment.style'
|
|
_inherit = ['mail.thread', 'mail.activity.mixin']
|
|
_description = 'Garment Style Master'
|
|
_order = 'name'
|
|
|
|
name = fields.Char(string='Style Number / Code', required=True, index=True, tracking=True)
|
|
style_name = fields.Char(string='Style Description / Name', required=True, tracking=True)
|
|
product_tmpl_id = fields.Many2one('product.template', string='Odoo Product Template', ondelete='set null')
|
|
active = fields.Boolean(default=True)
|
|
|
|
# Classification
|
|
brand_id = fields.Many2one('garment.brand', string='Brand', tracking=True)
|
|
season_id = fields.Many2one('garment.season', string='Season', tracking=True)
|
|
gender_id = fields.Many2one('garment.gender', string='Gender / Category')
|
|
garment_type_id = fields.Many2one('garment.type', string='Garment Type', required=True)
|
|
|
|
# Fabric Specifications (Tiruppur Specifics)
|
|
fabric_type_id = fields.Many2one('garment.fabric.type', string='Fabric Knit Type', required=True)
|
|
composition_id = fields.Many2one('garment.composition', string='Fabric Composition', required=True)
|
|
gsm = fields.Integer(string='Fabric GSM (g/m²)', required=True, default=180, help='Fabric Grams per Square Meter')
|
|
fabric_width = fields.Float(string='Fabric Width (Inches)', default=30.0, help='Tube Dia or Open Width')
|
|
width_type = fields.Selection([
|
|
('tube', 'Tubular / Dia (Circular Knit)'),
|
|
('open', 'Open Width (Slit & Heat Set)'),
|
|
], string='Width Type', default='tube', required=True)
|
|
shrinkage_length_pct = fields.Float(string='Shrinkage Length %', default=4.0)
|
|
shrinkage_width_pct = fields.Float(string='Shrinkage Width %', default=4.0)
|
|
yarn_count = fields.Char(string='Yarn Count')
|
|
|
|
# Dimensions & Variants
|
|
color_ids = fields.Many2many('garment.color', 'garment_style_color_rel', 'style_id', 'color_id', string='Available Colors', required=True)
|
|
size_ids = fields.Many2many('garment.size', 'garment_style_size_rel', 'style_id', 'size_id', string='Available Sizes', required=True)
|
|
uom_id = fields.Many2one('uom.uom', string='Unit of Measure', default=lambda self: self.env.ref('uom.product_uom_unit', raise_if_not_found=False))
|
|
|
|
# Industrial Engineering (IE)
|
|
sam = fields.Float(string='Sewing SAM (Minutes)', default=12.0, help='Standard Allowed Minutes / Standard Minute Value for sewing this style')
|
|
smv = fields.Float(string='Total Garment SMV', default=18.0, help='Total Standard Minute Value (Cutting + Sewing + Finishing)')
|
|
|
|
# Commercial & References
|
|
buyer_id = fields.Many2one('res.partner', string='Buyer / Customer', domain=[('customer_rank', '>', 0)])
|
|
customer_style_ref = fields.Char(string='Buyer Style Reference', help="Buyer's internal style code")
|
|
sample_ref = fields.Char(string='Sample / Proto Reference')
|
|
hsn_code = fields.Char(string='GST HSN Code', default='61091000', help='HSN 6109 for Cotton T-Shirts')
|
|
|
|
# Media & Tech Pack
|
|
image_front = fields.Binary(string='Front View Sketch / Photo')
|
|
image_back = fields.Binary(string='Back View Sketch / Photo')
|
|
techpack_attachment = fields.Binary(string='Tech Pack Document (PDF)')
|
|
techpack_filename = fields.Char(string='Tech Pack Filename')
|
|
notes = fields.Html(string='Style Notes & Construction Specs')
|
|
|
|
# Relational Lines & Computes
|
|
measurement_line_ids = fields.One2many('garment.style.measurement', 'style_id', string='Measurement Specifications')
|
|
variant_count = fields.Integer(string='Variant Count', compute='_compute_counts')
|
|
bom_count = fields.Integer(string='BOM Count', compute='_compute_counts')
|
|
order_count = fields.Integer(string='Orders Count', compute='_compute_counts')
|
|
production_count = fields.Integer(string='Production Count', compute='_compute_counts')
|
|
|
|
_name_uniq = models.Constraint('UNIQUE(name)', 'Style Code must be unique!')
|
|
|
|
@api.depends('color_ids', 'size_ids')
|
|
def _compute_counts(self):
|
|
for rec in self:
|
|
rec.variant_count = len(rec.color_ids) * len(rec.size_ids)
|
|
rec.bom_count = self.env['garment.bom'].search_count([('style_id', '=', rec.id)])
|
|
rec.order_count = self.env['sale.order'].search_count([('garment_style_id', '=', rec.id)])
|
|
rec.production_count = self.env['garment.production.plan'].search_count([('style_id', '=', rec.id)])
|
|
|
|
def action_generate_product_variants(self):
|
|
"""Creates or updates the Odoo Product Template and variants for this Style"""
|
|
self.ensure_one()
|
|
ProductTmpl = self.env['product.template']
|
|
ProductProduct = self.env['product.product']
|
|
|
|
if not self.product_tmpl_id:
|
|
tmpl_vals = {
|
|
'name': f"[{self.name}] {self.style_name}",
|
|
'type': 'consu',
|
|
'uom_id': self.uom_id.id if self.uom_id else self.env.ref('uom.product_uom_unit').id,
|
|
'image_1920': self.image_front,
|
|
}
|
|
if 'uom_po_id' in ProductTmpl._fields and self.uom_id:
|
|
tmpl_vals['uom_po_id'] = self.uom_id.id
|
|
if 'detailed_type' in ProductTmpl._fields:
|
|
tmpl_vals['detailed_type'] = 'product'
|
|
tmpl = ProductTmpl.create(tmpl_vals)
|
|
self.product_tmpl_id = tmpl.id
|
|
|
|
created_count = 0
|
|
for color in self.color_ids:
|
|
for size in self.size_ids:
|
|
sku = f"{self.name}-{color.code}-{size.code}"
|
|
variant_name = f"{self.style_name} ({color.name} / {size.name})"
|
|
existing = ProductProduct.search([('default_code', '=', sku)], limit=1)
|
|
if not existing:
|
|
ProductProduct.create({
|
|
'name': variant_name,
|
|
'default_code': sku,
|
|
'type': 'consu',
|
|
})
|
|
created_count += 1
|
|
return {
|
|
'type': 'ir.actions.client',
|
|
'tag': 'display_notification',
|
|
'params': {
|
|
'title': _('Product Variants Synchronized'),
|
|
'message': _('Successfully generated/verified %d garment product variants for Style %s.') % (created_count, self.name),
|
|
'sticky': False,
|
|
'type': 'success',
|
|
}
|
|
}
|
|
|
|
def action_view_variants(self):
|
|
self.ensure_one()
|
|
products = self.env['product.product'].search([('default_code', '=like', f"{self.name}-%")])
|
|
return {
|
|
'name': _('Style Variants (%s)') % self.name,
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': 'product.product',
|
|
'view_mode': 'list,form',
|
|
'domain': [('id', 'in', products.ids)],
|
|
}
|
|
|
|
def action_view_boms(self):
|
|
self.ensure_one()
|
|
return {
|
|
'name': _('Garment BOMs for %s') % self.name,
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': 'garment.bom',
|
|
'view_mode': 'list,form',
|
|
'domain': [('style_id', '=', self.id)],
|
|
'context': {'default_style_id': self.id},
|
|
}
|
|
|
|
def action_view_production(self):
|
|
self.ensure_one()
|
|
return {
|
|
'name': _('Production Plans for %s') % self.name,
|
|
'type': 'ir.actions.act_window',
|
|
'res_model': 'garment.production.plan',
|
|
'view_mode': 'list,form',
|
|
'domain': [('style_id', '=', self.id)],
|
|
'context': {'default_style_id': self.id},
|
|
}
|
|
|
|
|
|
class GarmentStyleMeasurement(models.Model):
|
|
_name = 'garment.style.measurement'
|
|
_description = 'Garment Point of Measurement (POM) Specification'
|
|
_order = 'sequence, pom_name'
|
|
|
|
style_id = fields.Many2one('garment.style', string='Style', required=True, ondelete='cascade')
|
|
sequence = fields.Integer(string='Seq', default=10)
|
|
pom_code = fields.Char(string='POM Code')
|
|
pom_name = fields.Char(string='Point of Measurement (POM)', required=True)
|
|
tolerance_plus = fields.Float(string='Tol (+)', default=0.5)
|
|
tolerance_minus = fields.Float(string='Tol (-)', default=0.5)
|
|
|
|
# Standard Grading Columns (in cm or inches)
|
|
val_xs = fields.Float(string='XS')
|
|
val_s = fields.Float(string='S')
|
|
val_m = fields.Float(string='M')
|
|
val_l = fields.Float(string='L')
|
|
val_xl = fields.Float(string='XL')
|
|
val_xxl = fields.Float(string='XXL')
|
|
val_3xl = fields.Float(string='3XL')
|
|
unit = fields.Selection([('inch', 'Inches'), ('cm', 'Centimeters')], string='Unit', default='cm')
|