first commit

This commit is contained in:
Alaguraj0361 2026-09-21 16:43:23 +05:30
commit c365322e3d
62 changed files with 7809 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# OS / Editor artifacts
.DS_Store
Thumbs.db
.vscode/
.idea/
*.swp
*.swo
# Logs
*.log

427
WORKFLOW_GUIDELINE.md Normal file
View File

@ -0,0 +1,427 @@
# Tiruppur Garment Manufacturing ERP — Complete Step-by-Step Workflow Guide
**Odoo 19 Community / Enterprise Edition**
*Industrial Apparel & Knitwear ERP Suite for Tiruppur Garment Manufacturers*
---
## Table of Contents
1. [End-to-End Workflow Architecture](#1-end-to-end-workflow-architecture)
2. [Phase 1: Base Masters & Factory Foundation](#phase-1-base-masters--factory-foundation)
3. [Phase 2: Style Master, POM Grading & Tech Pack](#phase-2-style-master-pom-grading--tech-pack)
4. [Phase 3: Garment Pre-Costing & Target Margin](#phase-3-garment-pre-costing--target-margin)
5. [Phase 4: Sales Orders & 2D Color × Size Order Matrix](#phase-4-sales-orders--2d-color--size-order-matrix)
6. [Phase 5: Garment Bill of Materials (BOM) & Routing](#phase-5-garment-bill-of-materials-bom--routing)
7. [Phase 6: Master Production Planning (PP)](#phase-6-master-production-planning-pp)
8. [Phase 7: Material Requirement Planning (MRP) & Purchase RFQs](#phase-7-material-requirement-planning-mrp--purchase-rfqs)
9. [Phase 8: Fabric Roll Inventory & 4-Point Quality Inspection](#phase-8-fabric-roll-inventory--4-point-quality-inspection)
10. [Phase 9: Cutting Room Lay Planning & Marker Ratio](#phase-9-cutting-room-lay-planning--marker-ratio)
11. [Phase 10: Barcode Bundle Generation & Traveler Tracking](#phase-10-barcode-bundle-generation--traveler-tracking)
12. [Phase 11: Sewing Floor & Real-Time Hourly Efficiency Logging](#phase-11-sewing-floor--real-time-hourly-efficiency-logging)
13. [Phase 12: Subcontracting & Job Work (Delivery Challans)](#phase-12-subcontracting--job-work-delivery-challans)
14. [Phase 13: Multi-Stage Quality Control (QC) & Rework Routing](#phase-13-multi-stage-quality-control-qc--rework-routing)
15. [Phase 14: Master Carton Packing, Finished Goods (FG) & Dispatch](#phase-14-master-carton-packing-finished-goods-fg--dispatch)
16. [Control Room: Factory Dashboard & Traceability Explorer](#control-room-factory-dashboard--traceability-explorer)
17. [Index of Standard Print Reports & Barcode Tickets](#index-of-standard-print-reports--barcode-tickets)
---
## 1. End-to-End Workflow Architecture
```mermaid
flowchart TD
M1[1. Base Masters: Colors, Sizes, Fabrics, Lines] --> S1[2. Garment Style & Tech Pack]
S1 --> C1[3. Pre-Costing Sheet]
S1 --> SO[4. Sales Order & 2D Matrix Grid]
SO --> PP[5. Production Master Plan]
PP --> BOM[6. Garment BOM & SAM Routing]
PP --> MRP[7. MRP & Shortage Purchase RFQ]
MRP --> FR[8. Fabric Roll Inward & 4-Point QC]
FR --> CP[9. Cutting Plan & Lay Ratio]
CP --> BND[10. Barcoded Bundle Tickets]
BND --> SEW[11. Sewing Line & Hourly Efficiency]
BND -.-> JW[12. Job Work Subcontracting: Dyeing/Print/Embroidery]
SEW --> QC[13. Endline QC & Rework Routing]
QC --> RW[13b. Rework Order & Repair]
RW --> QC
QC --> PCK[14. Export Carton Packing]
PCK --> FG[15. Finished Goods Inventory]
FG --> DSP[16. Dispatch & Shipping Logistics]
```
---
## Phase 1: Base Masters & Factory Foundation
Configure these fundamental building blocks before entering transactions:
* **Menu Path**: `Garment ERP > Configuration`
| Master Name | Technical Model | Purpose & Required Settings |
| :--- | :--- | :--- |
| **Garment Colors** | `garment.color` | Name, unique color code, color family group, Pantone reference (TPX/TCX), and Hex swatch code (e.g., `#000000` for Black, `#1E3A8A` for Navy). |
| **Garment Sizes** | `garment.size` | Size code (e.g., `XS`, `S`, `M`, `L`, `XL`, `2XL`), size category (Adult, Youth, Infant), and display sequence (controls column ordering in the 2D matrix). |
| **Fabric Knit Types** | `garment.fabric.type` | Knit structures (Single Jersey, 1x1 Rib, 2x2 Rib, Interlock, Pique Polo, Fleece, French Terry, Waffle) with target GSM ranges. |
| **Compositions** | `garment.composition` | Yarn composition percentages (e.g., 100% Cotton, 60/40 CVC, 95/5 Cotton Lycra). |
| **Brands & Seasons** | `garment.brand`, `garment.season` | Brand identities, logos, and seasonal milestones (e.g., `SS-2026`, `AW-2026`). |
| **Garment Types** | `garment.type` | Top wear, Bottom wear, Outerwear, Activewear, Innerwear. |
| **Defect Types** | `garment.defect.type` | Under `Quality & Rework > Defect Categories Master`. Stitching, Fabric, Measurement, Print/Embroidery, and Finishing defects with severity levels (Minor, Major, Critical). |
| **Sewing Lines** | `garment.sewing.line` | Under `Sewing Floor > Sewing Lines Configuration`. Line code, supervisor, operator count, helper count, daily target capacity. |
---
## Phase 2: Style Master, POM Grading & Tech Pack
The Style Master represents the garment's design, Point of Measurement (POM) grading chart, and SKU generator.
* **Menu Path**: `Garment ERP > Merchandising > Garment Styles`
### Step-by-Step Procedure:
1. Click **New** and enter:
* **Style Code**: Internal style code (e.g., `TSH-1001`).
* **Style Description**: Full commercial title (e.g., *Men's Crew Neck Bio-Washed T-Shirt*).
* **Classification**: Select Brand, Season, Gender, Garment Type, and Buyer.
* **Tiruppur Fabric Specs**:
* **Knit Structure**: e.g., Single Jersey.
* **Fabric Composition**: e.g., 100% Combed Cotton.
* **Fabric GSM**: e.g., `180` g/m².
* **Fabric Width**: e.g., `30.0` inches.
* **Width Type**: Select `Tubular / Dia` (Circular Knit) or `Open Width` (Slit & Stentered).
* **Shrinkage %**: Enter Length & Width expected shrinkage (default 4.0%).
* **Yarn Count**: e.g., `30s Combed`.
* **Industrial Engineering (IE)**:
* **Sewing SAM**: Standard Allowed Minutes (e.g., `12.0` min).
* **Total SMV**: Total Standard Minute Value including cutting and finishing (e.g., `18.0` min).
* **Colors & Sizes**: Pick all active colors and sizes for this style.
* **Media & Tech Pack**: Upload front/back sketch photos and attach the buyer's Tech Pack PDF.
* **Point of Measurement (POM) Specifications**:
* Add measurement lines: Chest Width, Body Length, Across Shoulder, Sleeve Length, Neck Drop, Bottom Hem, etc.
* Define `Tol (+)` and `Tol (-)` tolerances.
* Enter graded measurements across XS, S, M, L, XL, XXL, 3XL in cm or inches.
2. Click **Generate Product Variants** (Header button):
* Automatically generates standard Odoo `product.product` records with the SKU structure `[STYLE]-[COLOR]-[SIZE]` (e.g., `TSH-1001-BLK-M`).
* Product variants are immediately available for inventory tracking and BOM linking.
---
## Phase 3: Garment Pre-Costing & Target Margin
Calculate cost per piece, cost per dozen, overheads, and target profit margins.
* **Menu Path**: `Garment ERP > Merchandising > Garment Pre & Post Costing`
### Step-by-Step Procedure:
1. Click **New**, select the **Garment Style**, **Buyer**, and enter **Target Order Quantity** (e.g., `10,000` Pcs).
2. Set **Costing Stage**: Select `Pre-Costing (Quotation / Estimate)`.
3. If an approved BOM exists, click **Load from BOM** to pull fabric, trim, and labor costs automatically; otherwise enter:
* **Raw Materials**: Fabric cost / pc + Dyeing & Finishing cost / pc.
* **Trims & Accessories**: Labels, tags, polybags, cartons, threads.
* **Value Addition**: Chest print / Rotary print / Embroidery / Bio-washing.
* **Manufacturing CM (Cut & Make)**: Cutting labor / pc + Sewing CM labor / pc.
* **Packaging & Freight**: Polybags & Cartons / pc + Local freight & Port handling / pc.
* **Factory Overheads %**: e.g., `8.0%`.
4. Review the auto-computed metrics:
* **Total Garment Cost / Pc**
* **Total Garment Cost / Dozen**
* **Total Order Cost**
5. Enter **Target Selling Price / Pc** (e.g., `₹180.00`).
6. Inspect the auto-computed **Gross Margin / Pc**, **Gross Margin %**, and **Total Expected Profit**.
---
## Phase 4: Sales Orders & 2D Color × Size Order Matrix
Handle customer orders with multi-dimensional Color × Size distribution.
* **Menu Path**: `Garment ERP > Merchandising > Garment Sales Orders`
### Step-by-Step Procedure:
1. Click **New**, pick the **Buyer / Customer**, and ensure **Garment Manufacturing Order** is checked.
2. Select the **Garment Style** (this auto-fills Season, Customer Style Ref, and Buyer).
3. Set **Buyer PO Reference**, **Target Shipment Date**, **Ex-Factory Date**, and **Shipment Mode** (Sea, Air, Road).
4. Navigate to the **Size-Color Order Matrix** tab:
* Add lines selecting Color, Size, Quantity, and Unit Price.
* The live **HTML Order Matrix Summary** renders the full 2D Color vs Size table with color swatches, row subtotals, column subtotals, and total piece count.
5. Click **Sync Matrix to Order Lines** (Header button):
* Creates or updates native Odoo `sale.order.line` records linked to the specific variant SKUs (`product.product`).
6. Click **Confirm** to lock the Sales Order.
---
## Phase 5: Garment Bill of Materials (BOM) & Routing
Define raw material consumption and process routing per garment unit.
* **Menu Path**: `Garment ERP > Planning & MRP > Garment BOMs`
### Step-by-Step Procedure:
1. Click **New**, choose the **Garment Style**, and keep **Base Quantity** as `1.0 Pc`.
2. **Fabric Components Tab**:
* Add components: Body Panels (Front & Back), Neck Rib, Flat Collar, Pocket Panels, Sleeve Cuffs, Plackets, Hoods.
* Specify **Fabric Product**, **Net Consumption (Kg/Pc)** (e.g., `0.1800 Kg`), **Cutting Wastage %** (e.g., `5.0%`), and **Fabric Cost / Kg**.
* The system computes **Gross Consumption (Kg/Pc)** and **Fabric Cost / Pc**.
3. **Trims & Accessories Tab**:
* Add trims: Main Label, Size Label, Wash Care Label, Hang Tag, Price Tag, Polybag, Master Carton Box, Sewing Thread, Buttons, Zippers.
* Specify **Net Qty / Pc**, **Allowance %** (e.g., `3%`), and **Unit Cost**.
4. **Operations & Routing Tab**:
* Add manufacturing operations in sequential order across Cutting, Sewing, Job Work, Washing, Finishing, and Packing.
* Assign machine types (SNLS, 4T Overlock, 5T Overlock, Flatlock, Button Stitch, Steam Iron) with operation SAM and labor minute rate.
5. Save the BOM. The system calculates estimated garment production cost per piece.
---
## Phase 6: Master Production Planning (PP)
Master Production Planning links customer demand, floor scheduling, and shop floor execution.
* **Menu Path**: `Garment ERP > Planning & MRP > Production Plans`
*(Or click "Create Production Plan" inside the confirmed Sales Order)*
### Step-by-Step Procedure:
1. In the Production Plan, verify:
* **Sales Order Ref**, **Garment Style**, and **Garment BOM**.
* **Planned Quantity**, **Production Start Date**, **Target Delivery Date**, and **Priority**.
* The **Color/Size Production Targets** table auto-populates all matrix lines from the sales order.
2. Click **Confirm Plan** $\rightarrow$ Status changes to `Planned / Scheduled`.
3. Click **Start Production** $\rightarrow$ Status changes to `In Production`.
4. Smart buttons track progress in real time:
* **Cutting Orders**
* **Bundles**
* **Job Work Orders**
* **QC Inspections**
* **Packing Plans**
5. Progress bar and stage quantities (**Cut Qty**, **Sewn Qty**, **Packed Qty**, **Dispatched Qty**) update automatically from shop floor execution.
---
## Phase 7: Material Requirement Planning (MRP) & Purchase RFQs
Calculate gross material requirements, compare against on-hand stock, and generate purchase orders for shortages.
* **Menu Path**: Inside the Production Plan, click **Calculate Material Requirements (MRP)**
*(Or go to `Garment ERP > Planning & MRP > Material Requirements (MRP)`)*
### Step-by-Step Procedure:
1. Open the MRP record and click **Compute Material Requirements**:
* Multiplies planned garment pieces by BOM gross fabric consumption (Kg) and trim quantities.
* Compares required quantities against warehouse stock on hand (`qty_available`).
* Determines net **Shortage Quantity** and total procurement cost.
2. Click **Generate Purchase RFQs**:
* Automatically creates draft Purchase Orders (`purchase.order`) for all shortage materials grouped by vendor.
* Links generated RFQs directly to the MRP record for traceability.
---
## Phase 8: Fabric Roll Inventory & 4-Point Quality Inspection
Track knitted fabric roll inventory and enforce the international 4-point defect grading system before cutting.
* **Menu Path**: `Garment ERP > Fabric & Stores > Fabric Rolls`
### Step-by-Step Procedure:
1. **Inward Fabric Rolls**:
* Click **New** and record Roll Number, Fabric Product, Shade/Dyeing Lot #, Actual GSM, Width, Width Type (Tubular/Open Width), Gross Weight (Kg), Tare Weight (Kg), and Fabric Mill Supplier.
* Initial status is `Received / Uninspected`.
2. **Conduct 4-Point Inspection**:
* Click **Create 4-Point Inspection** (or go to `Fabric 4-Point Inspections`).
* Inspector verifies measured GSM, measured Width, and evaluates **Shade Grade** (`Grade A` to `Grade D`).
* Enter defect counts found across the inspected length based on standard 4-point defect rules:
* **1 Point**: Defect $\le$ 3 inches.
* **2 Points**: Defect > 3 up to 6 inches.
* **3 Points**: Defect > 6 up to 9 inches.
* **4 Points**: Defect > 9 inches or any hole.
* The system computes the defect score:
$$\text{Score per 100 } \text{m}^2 = \frac{\text{Total Defect Points} \times 100}{\text{Inspected Length (Meters)} \times \text{Fabric Width (Meters)}}$$
* **Pass/Fail Decision**:
* If $\text{Score} \le \text{Threshold (default 28 points)}$: Click **Quality Passed** $\rightarrow$ Roll status becomes **Quality Approved (Ready to Cut)**.
* If score exceeds threshold: Click **Quality Failed** $\rightarrow$ Roll status becomes **Quality Rejected (Quarantine)**.
* *Only Quality Approved rolls can be issued to cutting orders.*
---
## Phase 9: Cutting Room Lay Planning & Marker Ratio
Plan marker dimensions, fabric roll allocation, and cutting ratios.
* **Menu Path**: Inside the Production Plan, click **Create Cutting Order**
*(Or go to `Garment ERP > Cutting & Bundling > Cutting Orders`)*
### Step-by-Step Procedure:
1. On the Cutting Plan form:
* Select **Body Fabric Color**.
* Enter **Marker Length (Meters)**, **Marker Width (Inches)**, **Number of Plies / Layers** (e.g., `100`), and **Bundle Size** (default `25` pieces/bundle).
* Review the **Size Cut Ratio** lines (auto-populated from the production targets, e.g., S:1, M:2, L:2, XL:1).
* **Planned Cut Pieces** is computed: $\text{Plies Count} \times \sum(\text{Ratios})$.
2. **Fabric Roll Allocation Tab**:
* Select approved fabric rolls (`garment.fabric.roll`).
* Enter **Weight Issued (Kg)**, **Weight Consumed (Kg)**, and **End-Bit Scrap (Kg)**.
* The system computes **Fabric Wastage %** and deducts consumed weight from the fabric rolls.
3. Click **Start Cutting** $\rightarrow$ Status changes to `In Cutting`.
---
## Phase 10: Barcode Bundle Generation & Traveler Tracking
Split cut plies into serialized bundle travelers with barcode tickets.
* **Menu Path**: Inside the Cutting Plan, click **Generate Production Bundles**
### Step-by-Step Procedure:
1. Clicking **Generate Production Bundles**:
* Divides cut pieces into bundles conforming to the configured **Bundle Size** (e.g., 25 pcs).
* Creates serialized bundle records (`BND-XXXXX`) with ply ranges (e.g., Ply 125, Ply 2650) and unique barcodes.
* Sets Cutting Plan status to `Cut & Bundled`.
2. **Print Bundle Barcode Tickets**:
* Open the **Generated Bundles** smart button, select all bundles, and click **Print > Production Bundle Barcode Tickets**.
* Attach printed tickets to the cut bundles as physical travelers.
3. **Shop Floor Traveler Workflow**:
* As bundles move through manufacturing cells, operators scan or click:
* **Send to Sewing** $\rightarrow$ Stage: `Sewing Assembly`
* **Send to Job Work** $\rightarrow$ Stage: `External Processing`
* **Move to Quality** $\rightarrow$ Stage: `Quality Inspection`
* **Move to Packing** $\rightarrow$ Stage: `Packing Section`
* **Complete Packing** $\rightarrow$ Stage: `Finished Goods / Packed`
* Every movement creates an immutable entry in the bundle's **Operation Tracking History** table.
---
## Phase 11: Sewing Floor & Real-Time Hourly Efficiency Logging
Track hourly line outputs, defect counts, and operator efficiency.
* **Menu Path**: `Garment ERP > Sewing Floor > Hourly Production Logs`
### Step-by-Step Procedure:
1. Click **New** at the start of each production shift:
* Select **Sewing Line**, **Date**, **Shift**, **Garment Style Running**, and **Production Order**.
* Operators present and Style SAM auto-populate from the line and style masters.
2. Record output and defects at the end of each hour:
* Enter **Target**, **Actual Output**, and **Defect / Alteration Count** for hours H1 through H8 (+ OT).
3. Review Live Line Performance:
* The system computes day totals and line efficiency:
$$\text{Line Efficiency \%} = \frac{\text{Total Day Actual Output} \times \text{SAM}}{\text{Operators Present} \times \text{Hours Worked} \times 60} \times 100$$
* Awards an automated visual badge:
* **Excellent**: $\ge 90\%$
* **Good**: $75\% - 89\%$
* **Below Target**: $< 75\%$
---
## Phase 12: Subcontracting & Job Work (Delivery Challans)
Manage external operations for knitting, dyeing, chest print, embroidery, or bio-washing.
* **Menu Path**: `Garment ERP > Subcontracting / Job Work > Job Work Challans (DC)`
### Step-by-Step Procedure:
1. Click **New** and enter:
* **Job Worker / Subcontractor** partner.
* **Processing Operation**: Knitting, Dyeing, All-Over Print, Chest Print, Embroidery, Washing, Finishing.
* **Rate Basis** (Per Kg / Per Piece) and **Job Work Rate (INR)**.
* **Standard Process Loss %**: Allowed shrinkage/wastage during process (e.g., `4.0%`).
* Transport details: Vehicle Registration No, GST E-Way Bill Number, Expected Inward Date.
2. **Materials Sent Outward Tab**:
* Add fabric rolls or garment bundles with dispatched quantities.
* Click **Dispatch Material (Outward DC)** $\rightarrow$ Status changes to `Material Sent`.
* Print the official GST **Job Work Delivery Challan (DC)** report.
3. **Materials Inward Received Tab**:
* As processed goods return, record Date, Vendor DC No, Received Qty, and Rejected Qty.
* The system calculates **Accepted Good Qty**, **Pending Balance Qty**, **Actual Process Loss %**, and **Total Job Work Cost**.
4. Click **Complete Job Work** once all materials are returned.
---
## Phase 13: Multi-Stage Quality Control (QC) & Rework Routing
Inspect garments at key control gates and route defects for alteration.
* **Menu Path**: `Garment ERP > Quality & Rework > Quality Inspections (QC)`
### Step-by-Step Procedure:
1. Click **New** and choose the **Inspection Stage**:
* `Fabric Inspection`
* `Cutting Section QC`
* `Sewing Inline Roaming QC`
* `Sewing Endline 100% Checking`
* `Job Work Inward QC`
* `Final AQL 2.5 Pre-Shipment Audit`
2. Select Production Plan, Garment Style, Color, Size, Bundle Ref, and Sewing Line.
3. Enter **Lot Quantity**, **Sample Quantity Checked**, **Passed Quantity**, and **Failed Quantity**.
4. Under **Defect Details**, log identified flaws from the Defect Master (e.g., Broken Stitch, Skipped Stitch, Oil Spot, Needle Hole, Measurement Variance).
5. **Inspection Verdict**:
* **Pass**: Click **Pass Inspection** $\rightarrow$ Status updates to `Quality Approved`.
* **Rework**: Click **Trigger Rework Order**:
* Creates a linked `garment.rework.order` record.
* Verdict sets to `Rework Required`.
6. **Rework Order Execution** (`Quality & Rework > Rework Orders`):
* Responsible section assigned: Sewing alteration, Cutting panel replacement, Spot cleaning, or Job worker re-processing.
* Workflow stages: `Assigned` $\rightarrow$ `In Rework` $\rightarrow$ `Ready for QA Re-check` $\rightarrow$ `Passed QA Re-check` (restores pieces to production) or `Irreparable / Scrapped`.
---
## Phase 14: Master Carton Packing, Finished Goods (FG) & Dispatch
Pack garments into shipping cartons, update finished goods inventory, and dispatch.
### Part A: Carton Packing Plan
* **Menu Path**: `Garment ERP > Packing & Dispatch > Packing Plans`
1. Click **New**, select **Production Plan**, **Garment Style**, and **Carton Packing Method**:
* `Solid Color / Solid Size` (Single SKU per carton).
* `Solid Color / Assorted Size` (Ratio pack, e.g., 1S:2M:2L:1XL).
* `Assorted Color / Assorted Size` (Pre-pack mix).
2. Under **Master Cartons**, add cartons:
* Enter box dimensions: Length (cm), Width (cm), Height (cm). The system computes **CBM ($m^3$)**:
$$\text{CBM} = \frac{\text{Length} \times \text{Width} \times \text{Height}}{1,000,000}$$
* Enter Net Weight & Gross Weight.
* Under **Carton Breakdown**, enter the color and size pieces packed in each box.
3. Click **Transfer to Finished Goods**:
* Updates **Finished Goods Inventory** (`garment.finished.goods`) by variant SKU with on-hand quantities. Status updates to `Packed & Transferred to FG`.
### Part B: Dispatch & Shipping Logistics
* **Menu Path**: `Garment ERP > Packing & Dispatch > Dispatch & Shipments`
1. Click **New**, select **Buyer / Consignee**, **Sales Order**, and **Production Plan**.
2. Enter logistics info: Carrier / Shipping Line, B/L or Air Waybill No, Container No, Customs Seal No, Truck No, Port of Loading, and Port of Discharge.
3. In **Dispatched Cartons**, select all cartons loaded into the container or truck.
* Total cartons, total garments, total gross weight, and total shipment CBM are computed automatically.
4. Click **Dispatch Shipment**:
* Deducts shipped quantities from Finished Goods inventory and marks the shipment as `In Transit`.
5. Click **Mark Delivered** upon destination arrival.
---
## Control Room: Factory Dashboard & Traceability Explorer
### Factory Live Dashboard
* **Menu Path**: `Garment ERP > Dashboards > Factory Live Dashboard`
Monitors real-time shop floor performance across 4 operational domains:
* **Executive KPIs**: Active Garment Orders, Total Booked Pieces, Active Production Plans, Average Production Progress %.
* **Sewing Floor Efficiency**: Active Sewing Lines, Today's Output Target vs Actual, Average Line Efficiency %, Today's Alteration Count.
* **Inventory & Materials**: Fabric Stock (Kg), QC Approved Rolls, Finished Goods Stock (Pcs).
* **Operations & Subcontracting**: Pending Job Work Challans, Open Rework Orders, Cartons Ready for Dispatch.
### End-to-End Traceability Explorer
* **Menu Path**: `Garment ERP > Dashboards > Traceability Explorer`
Enter any tracking number (Buyer PO, Production Plan #, Roll Barcode, Bundle QR, or Carton #) to view complete forward and backward traceability:
$$\text{Buyer PO} \longrightarrow \text{Production Plan} \longrightarrow \text{Fabric Rolls} \longrightarrow \text{Cutting Orders} \longrightarrow \text{Bundles} \longrightarrow \text{Sewing Logs} \longrightarrow \text{QC Inspections} \longrightarrow \text{Cartons} \longrightarrow \text{Dispatch}$$
---
## Index of Standard Print Reports & Barcode Tickets
All reports are available via the standard **Print** menu on their respective documents:
| Document / Report | Technical Report ID | Primary Model | Contents |
| :--- | :--- | :--- | :--- |
| **Sales Order Matrix Report** | `report_garment_sales_matrix` | `sale.order` | Buyer PO, Style specs, 2D Color × Size grid, delivery terms. |
| **Production Master Plan** | `report_garment_production_plan` | `garment.production.plan` | Target quantities, style BOM reference, scheduled dates. |
| **Bundle Barcode Tickets** | `report_garment_bundle_barcode` | `garment.bundle` | Printable QR/Barcoded ply bundle stickers for floor tracking. |
| **4-Point Fabric Inspection** | `report_garment_fabric_inspection` | `garment.fabric.inspection` | Defect point scoring sheet and roll approval certificate. |
| **Cutting Lay Plan & Marker** | `report_garment_cutting_plan` | `garment.cutting.plan` | Marker length, ply count, size cut ratio, and roll allocation. |
| **Job Work Delivery Challan** | `report_garment_jobwork_challan` | `garment.jobwork.order` | GST-compliant outward delivery challan (DC) for subcontractors. |
| **Quality Inspection Report** | `report_garment_quality_inspection` | `garment.quality.inspection` | Stage defect breakdown, sample size, and rework instructions. |
| **Export Packing List** | `report_garment_packing_list` | `garment.packing.plan` | Box-by-box breakdown, CBM, weights, and shipping marks. |
| **Garment Costing Sheet** | `report_garment_costing` | `garment.costing` | Pre/Post costing analysis with component-wise margins. |
---
*© 2026 Chennora Technologies / Tiruppur Garment Manufacturing ERP Suite for Odoo 19.*

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import models

View File

@ -0,0 +1,142 @@
# -*- coding: utf-8 -*-
{
'name': 'Tiruppur Garment Manufacturing ERP',
'version': '19.0.1.0.0',
'category': 'Manufacturing/Apparel',
'summary': 'End-to-End Garment Manufacturing ERP for Tiruppur Apparel Industry',
'sequence': 1,
'description': """
Tiruppur Garment Manufacturing ERP for Odoo 19
===============================================
A complete, industrial ERP suite specifically built for Tiruppur knitwear & apparel manufacturers.
Features & Lifecycle Management:
---------------------------------
1. **Garment Master Data & Style Variants**:
- Garment Style, Brand, Season, Gender, Garment Type
- Knitwear Fabric Specifications (GSM, Tube/Open Width, Shrinkage %, Yarn Count)
- Size & Color Master with Swatches
- Point of Measurement (POM) Specs & Tech Pack management
- Auto-generation of Product Variants (Style + Color + Size)
2. **Customer & Sales Order Management with Order Matrix**:
- Dynamic 2D Size × Color × Quantity Order Matrix grid
- Real-time HTML matrix calculation and synchronization with standard sale.order.line
- Buyer PO & Customer Style tracking with delivery milestones
3. **Garment Bill of Materials (BOM) & Material Requisition**:
- Knitwear fabric consumption breakdown (Body, Neck Rib, Collar, Pocket) with cutting wastage %
- Trims & accessories (Labels, Tags, Polybags, Master Cartons, Threads)
- Industrial Engineering SAM routing per operation
- Material requirement calculator per order quantity
4. **Roll-Wise Fabric Inventory & 4-Point Inspection**:
- Roll number, weight (gross/net/tare), length, GSM, width, shade/dyeing lot
- 4-Point System quality defect scoring (1 to 4 points) with score per 100 sq. meters
- Automated quality approval / quarantine status
5. **Garment Material Requirement Planning (MRP)**:
- Order quantity vs on-hand fabric and trims comparison
- Shortage calculation and one-click purchase RFQ generation
6. **Cutting & Lay Planning**:
- Marker length, width, plies/layers, size ratio per ply
- Fabric roll allocation and scrap/end-bit wastage tracking
- One-click automatic Production Bundle generation
7. **Production Bundle Tracking with Barcode / QR**:
- Serialized bundles with ply range and barcode/QR tickets
- Stage-by-stage traveler through Cutting Sewing Job Work Quality Packing Completed
- Full movement history and operator audit trail
8. **Sewing Line & Hourly Production Efficiency**:
- Line configuration with operator & helper counts
- 8-hour floor logging (Target, Actual, Defects)
- Real-time efficiency % formula: (Output × SAM) / (Operators × Minutes) × 100
9. **Tiruppur Subcontracting / Job Work (Delivery Challan)**:
- External processing for Knitting, Dyeing, All-Over/Chest Printing, Embroidery, Bio-washing
- GST-compliant Delivery Challan (DC) with Outward & Inward tracking
- Automated process loss / shrinkage % audit
10. **Quality Control & Automatic Rework Routing**:
- Stages: Fabric, Cutting, Sewing Inline, Sewing Endline, Job Work Inward, Final AQL
- Defect master classification (Stitching, Fabric, Measurement, Finishing)
- Automatic Rework Order creation on failed audits with repair & QA re-check
11. **Export Carton Packing & Finished Goods**:
- Solid & Ratio (Assorted) carton packing
- Breakdown by Size × Color per box with CBM and weights
- Automatic stock transfer to Finished Goods inventory by variant
12. **Dispatch & Delivery**:
- Shipping lines, Carrier, Container #, Seal #, B/L or AWB tracking
- Deduction of finished goods inventory and sales order fulfillment
13. **Garment Pre-Costing & Post-Costing**:
- Fabric + Trims + CM Labor + Value Addition + Packaging + Overheads = Total Garment Cost
- Target selling price, gross margin amount and percentage
14. **Live Factory Dashboard & Traceability Explorer**:
- Real-time KPI control room for orders, line efficiency, roll stock, and QC
- Complete forward & backward traceability across all manufacturing stages
- 9+ Professional QWeb PDF Reports and Bundle Barcode Tickets
""",
'author': 'Chennora Technologies / Tiruppur Apparel ERP',
'website': 'https://www.chennora.com',
'license': 'LGPL-3',
'depends': [
'base',
'mail',
'sale_management',
'stock',
'mrp',
'purchase',
'account',
'hr',
],
'data': [
# Security
'security/garment_security.xml',
'security/ir.model.access.csv',
# Sequences
'data/garment_sequence_data.xml',
# Menus
'views/garment_menus.xml',
# Views
'views/garment_base_views.xml',
'views/garment_style_views.xml',
'views/sale_order_views.xml',
'views/garment_bom_views.xml',
'views/production_plan_views.xml',
'views/fabric_roll_views.xml',
'views/fabric_inspection_views.xml',
'views/mrp_requirement_views.xml',
'views/cutting_plan_views.xml',
'views/bundle_views.xml',
'views/sewing_line_views.xml',
'views/job_work_views.xml',
'views/quality_control_views.xml',
'views/rework_views.xml',
'views/packing_views.xml',
'views/finished_goods_views.xml',
'views/dispatch_views.xml',
'views/garment_costing_views.xml',
'views/garment_dashboard_views.xml',
'views/garment_traceability_views.xml',
# Reports
'report/garment_report_actions.xml',
'report/report_sales_matrix_template.xml',
'report/report_production_plan_template.xml',
'report/report_bundle_barcode_template.xml',
'report/report_fabric_inspection_template.xml',
'report/report_cutting_plan_template.xml',
'report/report_job_work_challan_template.xml',
'report/report_quality_inspection_template.xml',
'report/report_packing_list_template.xml',
'report/report_garment_costing_template.xml',
],
'installable': True,
'application': True,
'auto_install': False,
}

View File

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- Garment BOM Sequence -->
<record id="seq_garment_bom" model="ir.sequence">
<field name="name">Garment BOM</field>
<field name="code">garment.bom</field>
<field name="prefix">BOM/%(year)s/</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<!-- Garment Production Plan Sequence -->
<record id="seq_garment_production_plan" model="ir.sequence">
<field name="name">Garment Production Plan</field>
<field name="code">garment.production.plan</field>
<field name="prefix">PP/%(year)s/</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<!-- Fabric Roll Sequence -->
<record id="seq_garment_fabric_roll" model="ir.sequence">
<field name="name">Fabric Roll</field>
<field name="code">garment.fabric.roll</field>
<field name="prefix">ROLL-</field>
<field name="padding">6</field>
<field name="company_id" eval="False"/>
</record>
<!-- Fabric Inspection Sequence -->
<record id="seq_garment_fabric_inspection" model="ir.sequence">
<field name="name">Fabric 4-Point Inspection</field>
<field name="code">garment.fabric.inspection</field>
<field name="prefix">FINSP/%(year)s/</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<!-- MRP Requirement Sequence -->
<record id="seq_garment_mrp_requirement" model="ir.sequence">
<field name="name">Garment MRP Requirement</field>
<field name="code">garment.mrp.requirement</field>
<field name="prefix">MRP/%(year)s/</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<!-- Cutting Plan Sequence -->
<record id="seq_garment_cutting_plan" model="ir.sequence">
<field name="name">Garment Cutting Order</field>
<field name="code">garment.cutting.plan</field>
<field name="prefix">CUT/%(year)s/</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<!-- Bundle Sequence -->
<record id="seq_garment_bundle" model="ir.sequence">
<field name="name">Production Bundle</field>
<field name="code">garment.bundle</field>
<field name="prefix">BND/%(year)s/</field>
<field name="padding">6</field>
<field name="company_id" eval="False"/>
</record>
<!-- Job Work Order Sequence -->
<record id="seq_garment_jobwork_order" model="ir.sequence">
<field name="name">Job Work Delivery Challan</field>
<field name="code">garment.jobwork.order</field>
<field name="prefix">JW/%(year)s/</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<!-- Quality Inspection Sequence -->
<record id="seq_garment_quality_inspection" model="ir.sequence">
<field name="name">Garment Quality Inspection</field>
<field name="code">garment.quality.inspection</field>
<field name="prefix">QC/%(year)s/</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<!-- Rework Order Sequence -->
<record id="seq_garment_rework_order" model="ir.sequence">
<field name="name">Garment Rework Order</field>
<field name="code">garment.rework.order</field>
<field name="prefix">RWK/%(year)s/</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<!-- Packing Plan Sequence -->
<record id="seq_garment_packing_plan" model="ir.sequence">
<field name="name">Garment Packing Plan</field>
<field name="code">garment.packing.plan</field>
<field name="prefix">PACK/%(year)s/</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<!-- Carton Sequence -->
<record id="seq_garment_carton" model="ir.sequence">
<field name="name">Export Master Carton</field>
<field name="code">garment.carton</field>
<field name="prefix">CTN-</field>
<field name="padding">6</field>
<field name="company_id" eval="False"/>
</record>
<!-- Dispatch Sequence -->
<record id="seq_garment_dispatch" model="ir.sequence">
<field name="name">Garment Dispatch Order</field>
<field name="code">garment.dispatch</field>
<field name="prefix">DISP/%(year)s/</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
<!-- Costing Sequence -->
<record id="seq_garment_costing" model="ir.sequence">
<field name="name">Garment Cost Sheet</field>
<field name="code">garment.costing</field>
<field name="prefix">COST/%(year)s/</field>
<field name="padding">5</field>
<field name="company_id" eval="False"/>
</record>
</data>
</odoo>

View File

@ -0,0 +1,20 @@
# -*- coding: utf-8 -*-
from . import garment_base
from . import garment_style
from . import sale_order
from . import garment_bom
from . import production_plan
from . import fabric_roll
from . import fabric_inspection
from . import mrp_requirement
from . import cutting_plan
from . import bundle
from . import sewing_line
from . import job_work
from . import quality_control
from . import rework
from . import packing
from . import finished_goods
from . import dispatch
from . import garment_costing
from . import garment_dashboard

View File

@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class GarmentBundle(models.Model):
_name = 'garment.bundle'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Production Bundle Ticket'
_order = 'name desc'
name = fields.Char(string='Bundle Number', required=True, default=lambda self: _('New'), copy=False, tracking=True)
barcode = fields.Char(string='Barcode / QR Code', index=True, copy=False)
# Parent References
cutting_plan_id = fields.Many2one('garment.cutting.plan', string='Cutting Order', ondelete='cascade', tracking=True)
production_plan_id = fields.Many2one('garment.production.plan', string='Production Plan', required=True, tracking=True)
style_id = fields.Many2one('garment.style', string='Garment Style', required=True)
color_id = fields.Many2one('garment.color', string='Color', required=True)
size_id = fields.Many2one('garment.size', string='Size', required=True)
# Bundle Contents
quantity = fields.Integer(string='Bundle Quantity (Pcs)', required=True, default=25, tracking=True)
ply_start = fields.Integer(string='Ply Start #', default=1)
ply_end = fields.Integer(string='Ply End #', default=25)
# Production Routing Stage
stage = fields.Selection([
('cutting', '1. Cutting Room'),
('sewing', '2. Sewing Assembly'),
('jobwork', '3. External Processing / Job Work'),
('quality', '4. Quality Inspection'),
('packing', '5. Packing Section'),
('completed', '6. Finished Goods / Packed'),
], string='Production Stage', default='cutting', tracking=True, required=True)
status = fields.Selection([
('ready', 'Ready for Next Operation'),
('in_process', 'In Process'),
('on_hold', 'On Hold (Issue / Defect)'),
('completed', 'Stage Completed'),
], string='Status', default='ready', tracking=True)
current_line_id = fields.Many2one('garment.sewing.line', string='Assigned Sewing Line')
current_operator_id = fields.Many2one('res.users', string='Current Operator / Supervisor')
history_ids = fields.One2many('garment.bundle.history', 'bundle_id', string='Operation Tracking History')
@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.bundle') or _('BND-%s') % fields.Date.today().strftime('%Y%m%d')
if not vals.get('barcode'):
vals['barcode'] = vals['name']
records = super(GarmentBundle, self).create(vals_list)
for rec in records:
rec._log_history(rec.stage or 'cutting', 'Bundle Created from Cutting Plies')
return records
def _log_history(self, stage, remarks):
self.ensure_one()
self.env['garment.bundle.history'].create({
'bundle_id': self.id,
'stage': stage,
'user_id': self.env.user.id,
'sewing_line_id': self.current_line_id.id if self.current_line_id else False,
'quantity': self.quantity,
'remarks': remarks,
})
def action_move_to_sewing(self):
self.write({'stage': 'sewing', 'status': 'in_process'})
self._log_history('sewing', 'Transferred to Sewing Floor')
def action_move_to_jobwork(self):
self.write({'stage': 'jobwork', 'status': 'in_process'})
self._log_history('jobwork', 'Sent to External Subcontractor')
def action_move_to_quality(self):
self.write({'stage': 'quality', 'status': 'ready'})
self._log_history('quality', 'Delivered to Quality Inspection Table')
def action_move_to_packing(self):
self.write({'stage': 'packing', 'status': 'ready'})
self._log_history('packing', 'Quality Passed - Sent to Packing')
def action_complete_packing(self):
self.write({'stage': 'completed', 'status': 'completed'})
self._log_history('completed', 'Packed into Master Carton')
class GarmentBundleHistory(models.Model):
_name = 'garment.bundle.history'
_description = 'Bundle Movement History'
_order = 'timestamp desc'
bundle_id = fields.Many2one('garment.bundle', string='Bundle', required=True, ondelete='cascade')
timestamp = fields.Datetime(string='Timestamp', default=fields.Datetime.now, required=True)
stage = fields.Selection([
('cutting', 'Cutting'),
('sewing', 'Sewing'),
('jobwork', 'Job Work'),
('quality', 'Quality'),
('packing', 'Packing'),
('completed', 'Completed'),
], string='Stage', required=True)
user_id = fields.Many2one('res.users', string='Scanned / Updated By', default=lambda self: self.env.user)
sewing_line_id = fields.Many2one('garment.sewing.line', string='Sewing Line')
quantity = fields.Integer(string='Quantity Passed')
remarks = fields.Char(string='Remarks / Operation')

View File

@ -0,0 +1,212 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import UserError
class GarmentCuttingPlan(models.Model):
_name = 'garment.cutting.plan'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Garment Cutting Plan & Lay Management'
_order = 'name desc'
name = fields.Char(string='Cutting Order No', required=True, default=lambda self: _('New'), copy=False, tracking=True)
production_plan_id = fields.Many2one('garment.production.plan', string='Production Plan', required=True, tracking=True)
style_id = fields.Many2one('garment.style', string='Garment Style', related='production_plan_id.style_id', readonly=True)
color_id = fields.Many2one('garment.color', string='Body Fabric Color', required=True)
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
# Lay Planning & Marker
marker_no = fields.Char(string='Marker Code / Reference', default='MKR-01')
marker_length = fields.Float(string='Marker Length (Meters)', default=6.2)
marker_width = fields.Float(string='Marker Width (Inches)', default=30.0)
plies_count = fields.Integer(string='Number of Plies / Layers', default=100, tracking=True)
bundle_size = fields.Integer(string='Bundle Size (Pieces/Bundle)', default=25, help='Standard number of garments per bundle ticket')
# Quantitative Results
ratio_line_ids = fields.One2many('garment.cutting.ratio.line', 'cutting_plan_id', string='Size Cut Ratio')
roll_line_ids = fields.One2many('garment.cutting.roll.line', 'cutting_plan_id', string='Fabric Rolls Issued')
planned_cut_qty = fields.Integer(string='Planned Cut Pieces', compute='_compute_planned_cut_qty', store=True)
actual_cut_qty = fields.Integer(string='Actual Cut Pieces', default=0, tracking=True)
# Wastage Calculations
total_fabric_issued_kg = fields.Float(string='Total Fabric Issued (Kg)', compute='_compute_fabric_usage', store=True)
total_fabric_used_kg = fields.Float(string='Actual Fabric Consumed (Kg)', compute='_compute_fabric_usage', store=True)
end_bit_scrap_kg = fields.Float(string='End-Bits / Remnant Scrap (Kg)', compute='_compute_fabric_usage', store=True)
cutting_wastage_pct = fields.Float(string='Cutting Wastage %', compute='_compute_fabric_usage', store=True)
# State Workflow
state = fields.Selection([
('draft', 'Draft'),
('planned', 'Lay Planned'),
('cutting', 'In Cutting'),
('completed', 'Cut & Bundled'),
('cancelled', 'Cancelled'),
], string='Status', default='draft', tracking=True, required=True)
bundle_ids = fields.One2many('garment.bundle', 'cutting_plan_id', string='Generated Bundles')
bundle_count = fields.Integer(string='Bundles Count', compute='_compute_bundle_count')
@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.cutting.plan') or _('CUT/%s') % fields.Date.today().strftime('%Y%m%d')
plans = super().create(vals_list)
for plan in plans:
if not plan.ratio_line_ids and plan.production_plan_id:
plan._populate_ratios_from_plan()
return plans
def _populate_ratios_from_plan(self):
self.ensure_one()
matching_lines = self.production_plan_id.line_ids.filtered(lambda l: l.color_id.id == self.color_id.id)
if not matching_lines:
matching_lines = self.production_plan_id.line_ids
lines = []
for pl in matching_lines:
lines.append((0, 0, {
'size_id': pl.size_id.id,
'ratio': 1,
}))
if lines:
self.write({'ratio_line_ids': lines})
@api.depends('plies_count', 'ratio_line_ids.ratio')
def _compute_planned_cut_qty(self):
for plan in self:
total_ratio = sum(plan.ratio_line_ids.mapped('ratio'))
plan.planned_cut_qty = plan.plies_count * total_ratio
@api.depends('roll_line_ids.weight_issued', 'roll_line_ids.weight_used', 'roll_line_ids.end_bit_scrap')
def _compute_fabric_usage(self):
for plan in self:
issued = sum(plan.roll_line_ids.mapped('weight_issued'))
used = sum(plan.roll_line_ids.mapped('weight_used'))
scrap = sum(plan.roll_line_ids.mapped('end_bit_scrap'))
plan.total_fabric_issued_kg = issued
plan.total_fabric_used_kg = used
plan.end_bit_scrap_kg = scrap
if used > 0:
plan.cutting_wastage_pct = round((scrap / (used + scrap)) * 100.0, 2)
else:
plan.cutting_wastage_pct = 0.0
@api.depends('bundle_ids')
def _compute_bundle_count(self):
for plan in self:
plan.bundle_count = len(plan.bundle_ids)
def action_start_cutting(self):
self.write({'state': 'cutting'})
def action_generate_bundles(self):
"""Generates production bundles based on lay plies and size ratios"""
self.ensure_one()
if not self.ratio_line_ids:
raise UserError(_("Please define Size Cut Ratios before generating bundles."))
if self.plies_count <= 0:
raise UserError(_("Plies count must be greater than 0."))
Bundle = self.env['garment.bundle']
bundle_size = self.bundle_size or 25
created_bundles = []
total_actual = 0
for r_line in self.ratio_line_ids:
if r_line.ratio <= 0:
continue
total_pieces_for_size = self.plies_count * r_line.ratio
total_actual += total_pieces_for_size
# Divide into bundles of bundle_size
remaining = total_pieces_for_size
ply_cursor = 1
bundle_idx = 1
while remaining > 0:
cur_bundle_qty = min(bundle_size, remaining)
bundle_vals = {
'cutting_plan_id': self.id,
'production_plan_id': self.production_plan_id.id,
'style_id': self.style_id.id,
'color_id': self.color_id.id,
'size_id': r_line.size_id.id,
'quantity': cur_bundle_qty,
'ply_start': ply_cursor,
'ply_end': ply_cursor + cur_bundle_qty - 1,
'stage': 'sewing',
'status': 'ready',
}
b = Bundle.create(bundle_vals)
created_bundles.append(b.id)
remaining -= cur_bundle_qty
ply_cursor += cur_bundle_qty
bundle_idx += 1
self.write({
'actual_cut_qty': total_actual,
'state': 'completed',
})
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Bundles Generated'),
'message': _('Successfully generated %d production bundles for %d cut pieces.') % (len(created_bundles), total_actual),
'sticky': False,
'type': 'success',
}
}
def action_view_bundles(self):
self.ensure_one()
return {
'name': _('Bundles for %s') % self.name,
'type': 'ir.actions.act_window',
'res_model': 'garment.bundle',
'view_mode': 'list,form',
'domain': [('cutting_plan_id', '=', self.id)],
}
class GarmentCuttingRatioLine(models.Model):
_name = 'garment.cutting.ratio.line'
_description = 'Cutting Size Ratio Line'
_order = 'size_id'
cutting_plan_id = fields.Many2one('garment.cutting.plan', string='Cutting Plan', required=True, ondelete='cascade')
size_id = fields.Many2one('garment.size', string='Size', required=True)
ratio = fields.Integer(string='Ratio per Ply', default=1, required=True)
expected_cut_qty = fields.Integer(string='Expected Cut Pcs', compute='_compute_expected', store=True)
@api.depends('ratio', 'cutting_plan_id.plies_count')
def _compute_expected(self):
for line in self:
line.expected_cut_qty = line.ratio * (line.cutting_plan_id.plies_count or 0)
class GarmentCuttingRollLine(models.Model):
_name = 'garment.cutting.roll.line'
_description = 'Cutting Fabric Roll Allocation'
cutting_plan_id = fields.Many2one('garment.cutting.plan', string='Cutting Plan', required=True, ondelete='cascade')
roll_id = fields.Many2one('garment.fabric.roll', string='Fabric Roll', required=True, domain=[('status', 'in', ['approved', 'issued'])])
shade_lot = fields.Char(string='Shade Lot', related='roll_id.shade_lot', readonly=True)
weight_issued = fields.Float(string='Weight Issued (Kg)', required=True, default=24.0)
weight_used = fields.Float(string='Weight Consumed (Kg)', required=True, default=23.0)
end_bit_scrap = fields.Float(string='End-Bit Scrap (Kg)', default=1.0)
@api.onchange('roll_id')
def _onchange_roll_id(self):
if self.roll_id:
self.weight_issued = self.roll_id.remaining_weight
self.weight_used = self.roll_id.remaining_weight
@api.model_create_multi
def create(self, vals_list):
records = super(GarmentCuttingRollLine, self).create(vals_list)
for rec in records:
if rec.roll_id:
rec.roll_id.issued_weight += rec.weight_used
rec.roll_id.scrap_weight += rec.end_bit_scrap
rec.roll_id.status = 'issued'
return records

View File

@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import UserError
class GarmentDispatch(models.Model):
_name = 'garment.dispatch'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Garment Dispatch & Delivery Management'
_order = 'name desc'
name = fields.Char(string='Dispatch Order No', required=True, default=lambda self: _('New'), copy=False, tracking=True)
customer_id = fields.Many2one('res.partner', string='Buyer / Consignee', required=True, tracking=True)
sale_order_id = fields.Many2one('sale.order', string='Sales Order Ref', tracking=True)
production_plan_id = fields.Many2one('garment.production.plan', string='Production Plan Ref', tracking=True)
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
# Dates & Logistics
date_dispatch = fields.Date(string='Dispatch Date', default=fields.Date.today, tracking=True)
carrier_name = fields.Char(string='Carrier / Shipping Line')
bl_awb_no = fields.Char(string='B/L or Air Waybill No', tracking=True)
container_no = fields.Char(string='Container No (FCL)')
seal_no = fields.Char(string='Customs Seal No')
vehicle_no = fields.Char(string='Truck / Vehicle Registration No')
port_of_loading = fields.Char(string='Port of Loading', default='Tuticorin / Chennai Port')
port_of_discharge = fields.Char(string='Port of Discharge')
# Cartons & Metrics
carton_ids = fields.One2many('garment.carton', 'dispatch_id', string='Dispatched Cartons')
total_cartons = fields.Integer(string='Total Cartons', compute='_compute_totals', store=True)
total_pieces = fields.Integer(string='Total Garments Dispatched', compute='_compute_totals', store=True)
total_gross_weight = fields.Float(string='Total Gross Weight (Kg)', compute='_compute_totals', store=True)
total_cbm = fields.Float(string='Total CBM (m³)', compute='_compute_totals', store=True)
state = fields.Selection([
('draft', 'Draft Shipment'),
('packed', 'Loaded & Ready to Depart'),
('dispatched', 'Dispatched / In Transit'),
('delivered', 'Delivered to Customer'),
('cancelled', 'Cancelled'),
], string='Status', default='draft', tracking=True, required=True)
notes = fields.Text(string='Shipping Instructions / Marks & Numbers')
@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.dispatch') or _('DISP/%s') % fields.Date.today().strftime('%Y%m%d')
return super(GarmentDispatch, self).create(vals_list)
@api.depends('carton_ids.total_pieces', 'carton_ids.gross_weight_kg', 'carton_ids.cbm')
def _compute_totals(self):
for rec in self:
rec.total_cartons = len(rec.carton_ids)
rec.total_pieces = sum(rec.carton_ids.mapped('total_pieces'))
rec.total_gross_weight = round(sum(rec.carton_ids.mapped('gross_weight_kg')), 2)
rec.total_cbm = round(sum(rec.carton_ids.mapped('cbm')), 3)
def action_dispatch(self):
"""Dispatches the shipment, deducts Finished Goods inventory"""
self.ensure_one()
if not self.carton_ids:
raise UserError(_("Please attach cartons to dispatch."))
FG = self.env['garment.finished.goods']
for ctn in self.carton_ids:
for line in ctn.line_ids:
fg = FG.search([
('style_id', '=', ctn.style_id.id),
('color_id', '=', line.color_id.id),
('size_id', '=', line.size_id.id),
], limit=1)
if fg:
fg.write({
'on_hand_qty': max(0, fg.on_hand_qty - line.quantity),
'shipped_qty': fg.shipped_qty + line.quantity,
})
self.write({'state': 'dispatched'})
def action_mark_delivered(self):
self.write({'state': 'delivered'})

View File

@ -0,0 +1,101 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class GarmentFabricInspection(models.Model):
_name = 'garment.fabric.inspection'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Fabric 4-Point System Quality Inspection'
_order = 'name desc'
name = fields.Char(string='Inspection Number', required=True, default=lambda self: _('New'), copy=False)
roll_id = fields.Many2one('garment.fabric.roll', string='Fabric Roll', required=True, tracking=True)
supplier_id = fields.Many2one('res.partner', string='Supplier', related='roll_id.supplier_id', readonly=True)
fabric_product_id = fields.Many2one('product.product', string='Fabric Product', related='roll_id.product_id', readonly=True)
shade_lot = fields.Char(string='Shade / Dyeing Lot', related='roll_id.shade_lot', readonly=True)
# Inspector & Date
inspector_id = fields.Many2one('res.users', string='Quality Inspector', default=lambda self: self.env.user, tracking=True)
date_inspected = fields.Date(string='Inspection Date', default=fields.Date.today, tracking=True)
# GSM & Width Verification
target_gsm = fields.Integer(string='Target GSM', default=180)
actual_gsm = fields.Integer(string='Actual GSM Measured', default=180)
target_width = fields.Float(string='Target Width (Inches)', default=30.0)
actual_width = fields.Float(string='Actual Width (Inches)', default=30.0)
# Shade Evaluation
shade_grade = fields.Selection([
('grade_a', 'Grade A - Exact Match with Master Swatch'),
('grade_b', 'Grade B - Slight Variation (Approved)'),
('grade_c', 'Grade C - Noticeable Variation (Group Separately)'),
('grade_d', 'Grade D - Severely Off-Shade (Rejected)'),
], string='Shade / Color Evaluation', default='grade_a', required=True, tracking=True)
# 4-Point Defect Scoring System
# Points 1 to 4 based on defect length:
# 1 pt: Defect up to 3 inches
# 2 pts: Defect over 3 up to 6 inches
# 3 pts: Defect over 6 up to 9 inches
# 4 pts: Defect over 9 inches or any hole
defects_1pt_count = fields.Integer(string='1-Point Defects (<=3")', default=0)
defects_2pt_count = fields.Integer(string='2-Point Defects (3"-6")', default=0)
defects_3pt_count = fields.Integer(string='3-Point Defects (6"-9")', default=0)
defects_4pt_count = fields.Integer(string='4-Point Defects (>9" or Holes)', default=0)
total_defect_points = fields.Integer(string='Total Defect Points', compute='_compute_points', store=True)
inspected_meters = fields.Float(string='Inspected Length (Meters)', default=20.0, required=True)
# 4-Point Formula: (Total Points * 100) / (Inspected Meters * Fabric Width in Meters)
score_per_100_sqm = fields.Float(string='Score / 100 Sq. Meters', compute='_compute_points', store=True)
max_allowed_score = fields.Float(string='AQL Max Points Threshold', default=28.0, help='Standard industry threshold: 28 points per 100 sq meters')
# Quantitative Results
passed_meters = fields.Float(string='Passed Quantity (Meters)')
rejected_meters = fields.Float(string='Rejected Quantity (Meters)')
# Status
state = fields.Selection([
('draft', 'Draft'),
('inspected', 'Inspected'),
('passed', 'Quality Passed'),
('failed', 'Quality Failed / Rejected'),
], string='Inspection Verdict', default='draft', tracking=True, required=True)
remarks = fields.Text(string='Inspector Remarks / Observations')
@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.fabric.inspection') or _('FINSP/%s') % fields.Date.today().strftime('%Y%m%d')
return super().create(vals_list)
@api.depends('defects_1pt_count', 'defects_2pt_count', 'defects_3pt_count', 'defects_4pt_count', 'inspected_meters', 'actual_width')
def _compute_points(self):
for insp in self:
total_pts = (insp.defects_1pt_count * 1) + (insp.defects_2pt_count * 2) + (insp.defects_3pt_count * 3) + (insp.defects_4pt_count * 4)
insp.total_defect_points = total_pts
# Width in meters: width in inches * 0.0254
width_meters = (insp.actual_width or 30.0) * 0.0254
area_sqm = (insp.inspected_meters or 1.0) * width_meters
if area_sqm > 0:
insp.score_per_100_sqm = round((total_pts * 100.0) / area_sqm, 1)
else:
insp.score_per_100_sqm = 0.0
def action_pass(self):
self.ensure_one()
self.write({
'state': 'passed',
'passed_meters': self.roll_id.length_meters,
'rejected_meters': 0.0,
})
self.roll_id.write({'status': 'approved'})
def action_fail(self):
self.ensure_one()
self.write({
'state': 'failed',
'passed_meters': 0.0,
'rejected_meters': self.roll_id.length_meters,
})
self.roll_id.write({'status': 'rejected'})

View File

@ -0,0 +1,95 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class GarmentFabricRoll(models.Model):
_name = 'garment.fabric.roll'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Fabric Roll Inventory Tracking'
_order = 'name desc'
name = fields.Char(string='Roll Number', required=True, default=lambda self: _('New'), copy=False, tracking=True)
product_id = fields.Many2one('product.product', string='Fabric Product', required=True)
fabric_type_id = fields.Many2one('garment.fabric.type', string='Fabric Structure')
color_id = fields.Many2one('garment.color', string='Color / Shade')
shade_lot = fields.Char(string='Shade / Dyeing Lot Number', required=True, index=True, tracking=True)
barcode = fields.Char(string='Barcode / QR Code', index=True, copy=False)
# Technical Specifications
gsm = fields.Integer(string='GSM (Actual)', required=True, default=180)
width = fields.Float(string='Width (Inches)', required=True, default=30.0)
width_type = fields.Selection([('tube', 'Tubular'), ('open', 'Open Width')], string='Width Type', default='tube')
yarn_count = fields.Char(string='Yarn Count', default='30s Combed')
# Quantitative Weights (Kg)
gross_weight = fields.Float(string='Gross Weight (Kg)', required=True, default=25.0)
tare_weight = fields.Float(string='Tare / Core Weight (Kg)', default=0.8)
net_weight = fields.Float(string='Initial Net Weight (Kg)', compute='_compute_net_weight', store=True)
length_meters = fields.Float(string='Fabric Length (Meters)', default=75.0)
# Consumption & Balance
issued_weight = fields.Float(string='Issued Weight (Kg)', default=0.0)
scrap_weight = fields.Float(string='End-Bit Scrap (Kg)', default=0.0)
remaining_weight = fields.Float(string='Remaining Weight (Kg)', compute='_compute_remaining_weight', store=True)
# Origin & Storage
supplier_id = fields.Many2one('res.partner', string='Fabric Mill / Supplier', domain=[('supplier_rank', '>', 0)])
purchase_order_id = fields.Many2one('purchase.order', string='Purchase Order Ref')
date_received = fields.Date(string='Date Received', default=fields.Date.today)
location_id = fields.Many2one('stock.location', string='Storage Warehouse Location')
# Workflow State
status = fields.Selection([
('received', 'Received / Uninspected'),
('inspected', 'Inspected'),
('approved', 'Quality Approved (Ready to Cut)'),
('issued', 'Partially Issued'),
('exhausted', 'Fully Exhausted'),
('rejected', 'Quality Rejected (Quarantine)'),
], string='Roll Status', default='received', tracking=True, required=True)
# Inspections
inspection_ids = fields.One2many('garment.fabric.inspection', 'roll_id', string='Fabric Inspections')
inspection_count = fields.Integer(string='Inspections', compute='_compute_inspection_count')
@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.fabric.roll') or _('ROLL-%s') % fields.Date.today().strftime('%Y%m%d')
if not vals.get('barcode'):
vals['barcode'] = vals['name']
return super().create(vals_list)
@api.depends('gross_weight', 'tare_weight')
def _compute_net_weight(self):
for roll in self:
roll.net_weight = max(0.0, roll.gross_weight - roll.tare_weight)
@api.depends('net_weight', 'issued_weight', 'scrap_weight')
def _compute_remaining_weight(self):
for roll in self:
rem = roll.net_weight - roll.issued_weight - roll.scrap_weight
roll.remaining_weight = max(0.0, round(rem, 2))
if roll.remaining_weight <= 0.2 and roll.status in ['approved', 'issued']:
roll.status = 'exhausted'
@api.depends('inspection_ids')
def _compute_inspection_count(self):
for roll in self:
roll.inspection_count = len(roll.inspection_ids)
def action_create_inspection(self):
self.ensure_one()
insp = self.env['garment.fabric.inspection'].create({
'roll_id': self.id,
'target_gsm': self.gsm,
'target_width': self.width,
'inspected_meters': min(20.0, self.length_meters),
})
return {
'name': _('Fabric 4-Point Inspection'),
'type': 'ir.actions.act_window',
'res_model': 'garment.fabric.inspection',
'res_id': insp.id,
'view_mode': 'form',
}

View File

@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class GarmentFinishedGoods(models.Model):
_name = 'garment.finished.goods'
_description = 'Finished Goods Inventory by Variant (Style + Color + Size)'
_order = 'style_id, color_id, size_id'
style_id = fields.Many2one('garment.style', string='Garment Style', required=True, index=True)
color_id = fields.Many2one('garment.color', string='Color', required=True, index=True)
size_id = fields.Many2one('garment.size', string='Size', required=True, index=True)
sku = fields.Char(string='Variant SKU', compute='_compute_sku', store=True, index=True)
product_id = fields.Many2one('product.product', string='Odoo Variant Product')
location_id = fields.Many2one('stock.location', string='Warehouse Stock Location')
on_hand_qty = fields.Integer(string='On Hand Pieces', default=0)
reserved_qty = fields.Integer(string='Reserved Pieces', default=0)
available_qty = fields.Integer(string='Available to Ship', compute='_compute_available_qty', store=True)
shipped_qty = fields.Integer(string='Total Shipped Pieces', default=0)
_variant_uniq = models.Constraint('UNIQUE(style_id, color_id, size_id)', 'Finished Goods variant record already exists!')
@api.depends('style_id', 'color_id', 'size_id')
def _compute_sku(self):
for rec in self:
s_name = rec.style_id.name if rec.style_id else 'STYLE'
c_code = rec.color_id.code if rec.color_id else 'CLR'
sz_code = rec.size_id.code if rec.size_id else 'SZ'
rec.sku = f"{s_name}-{c_code}-{sz_code}"
@api.depends('on_hand_qty', 'reserved_qty')
def _compute_available_qty(self):
for rec in self:
rec.available_qty = max(0, rec.on_hand_qty - rec.reserved_qty)

View File

@ -0,0 +1,152 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class GarmentBrand(models.Model):
_name = 'garment.brand'
_description = 'Garment Brand'
_order = 'name'
name = fields.Char(string='Brand Name', required=True, index=True)
code = fields.Char(string='Brand Code', required=True)
logo = fields.Binary(string='Brand Logo')
active = fields.Boolean(default=True)
notes = fields.Text(string='Notes')
_code_uniq = models.Constraint('UNIQUE(code)', 'Brand Code must be unique!')
class GarmentSeason(models.Model):
_name = 'garment.season'
_description = 'Garment Season'
_order = 'year desc, name'
name = fields.Char(string='Season Name', required=True)
code = fields.Char(string='Season Code', required=True)
year = fields.Char(string='Year', default=lambda self: fields.Date.today().strftime('%Y'))
active = fields.Boolean(default=True)
_code_uniq = models.Constraint('UNIQUE(code)', 'Season Code must be unique!')
class GarmentGender(models.Model):
_name = 'garment.gender'
_description = 'Garment Gender Category'
_order = 'sequence, name'
name = fields.Char(string='Gender / Age Group', required=True)
code = fields.Char(string='Code', required=True)
sequence = fields.Integer(string='Sequence', default=10)
_code_uniq = models.Constraint('UNIQUE(code)', 'Gender Code must be unique!')
class GarmentType(models.Model):
_name = 'garment.type'
_description = 'Garment Product Type'
_order = 'name'
name = fields.Char(string='Garment Type', required=True)
code = fields.Char(string='Code', required=True)
category = fields.Selection([
('top', 'Top Wear'),
('bottom', 'Bottom Wear'),
('outerwear', 'Outerwear / Jackets'),
('innerwear', 'Innerwear / Undergarments'),
('activewear', 'Activewear / Sportswear'),
('accessories', 'Accessories'),
], string='Category', default='top', required=True)
description = fields.Text(string='Description')
_code_uniq = models.Constraint('UNIQUE(code)', 'Garment Type Code must be unique!')
class GarmentFabricType(models.Model):
_name = 'garment.fabric.type'
_description = 'Fabric Type / Structure'
_order = 'name'
name = fields.Char(string='Fabric Type Name', required=True)
code = fields.Char(string='Code', required=True)
structure = fields.Selection([
('single_jersey', 'Single Jersey'),
('rib_1x1', '1x1 Rib'),
('rib_2x2', '2x2 Rib'),
('interlock', 'Interlock'),
('pique', 'Pique / Polo Knit'),
('fleece', 'Fleece'),
('french_terry', 'French Terry'),
('waffle', 'Waffle Knit'),
('woven', 'Woven Fabric'),
('other', 'Other'),
], string='Knit Structure', default='single_jersey', required=True)
gsm_min = fields.Integer(string='Min GSM', default=140)
gsm_max = fields.Integer(string='Max GSM', default=220)
description = fields.Text(string='Description')
_code_uniq = models.Constraint('UNIQUE(code)', 'Fabric Type Code must be unique!')
class GarmentComposition(models.Model):
_name = 'garment.composition'
_description = 'Fabric Composition'
_order = 'name'
name = fields.Char(string='Composition', required=True)
code = fields.Char(string='Code', required=True)
cotton_pct = fields.Float(string='Cotton %', default=100.0)
polyester_pct = fields.Float(string='Polyester %', default=0.0)
spandex_pct = fields.Float(string='Spandex / Lycra %', default=0.0)
viscose_pct = fields.Float(string='Viscose %', default=0.0)
other_pct = fields.Float(string='Other Fiber %', default=0.0)
_code_uniq = models.Constraint('UNIQUE(code)', 'Composition Code must be unique!')
class GarmentColor(models.Model):
_name = 'garment.color'
_description = 'Garment Color Master'
_order = 'name'
name = fields.Char(string='Color Name', required=True, index=True)
code = fields.Char(string='Color Code', required=True, index=True)
color_group = fields.Selection([
('black', 'Blacks'),
('white', 'Whites & Off-Whites'),
('grey', 'Greys & Melanges'),
('navy', 'Navies & Blues'),
('red', 'Reds & Maroons'),
('green', 'Greens & Olives'),
('yellow', 'Yellows & Ochers'),
('pastel', 'Pastels'),
('multicolor', 'Printed / Multi'),
], string='Color Group', default='black')
pantone_code = fields.Char(string='Pantone Reference (TPX/TCX)')
color_hex = fields.Char(string='Hex Swatch Code', default='#000000')
active = fields.Boolean(default=True)
_code_uniq = models.Constraint('UNIQUE(code)', 'Color Code must be unique!')
def name_get(self):
result = []
for rec in self:
result.append((rec.id, f"[{rec.code}] {rec.name}"))
return result
class GarmentSize(models.Model):
_name = 'garment.size'
_description = 'Garment Size Master'
_order = 'sequence, code'
name = fields.Char(string='Size Name', required=True)
code = fields.Char(string='Size Code', required=True, index=True)
sequence = fields.Integer(string='Display Sequence', default=10, help='Used for sorting columns in Size Matrix')
size_group = fields.Selection([
('adult', 'Adult / Standard Alpha (XS-3XL)'),
('numeric_adult', 'Adult Numeric (28-44)'),
('youth', 'Kids / Youth (4Y-16Y)'),
('infant', 'Infant / Baby (0M-24M)'),
], string='Size Group', default='adult', required=True)
active = fields.Boolean(default=True)
_code_uniq = models.Constraint('UNIQUE(code)', 'Size Code must be unique!')

View File

@ -0,0 +1,199 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class GarmentBom(models.Model):
_name = 'garment.bom'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Garment Bill of Materials (BOM)'
_order = 'name desc'
name = fields.Char(string='BOM Reference', required=True, default=lambda self: _('New'), copy=False)
style_id = fields.Many2one('garment.style', string='Garment Style', required=True, tracking=True)
base_qty = fields.Float(string='Base Quantity', default=1.0, required=True, help='Number of garment pieces this BOM defines')
uom_id = fields.Many2one('uom.uom', string='UoM', related='style_id.uom_id', readonly=True)
active = fields.Boolean(default=True)
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
notes = fields.Text(string='Notes / Engineering Specs')
# BOM Lines
fabric_line_ids = fields.One2many('garment.bom.fabric', 'bom_id', string='Fabric Components')
trim_line_ids = fields.One2many('garment.bom.trim', 'bom_id', string='Trims & Accessories')
operation_line_ids = fields.One2many('garment.bom.operation', 'bom_id', string='Operations & Routing')
# Cost Summary Computes
total_fabric_cost = fields.Float(string='Total Fabric Cost', compute='_compute_totals', store=True)
total_trim_cost = fields.Float(string='Total Trims Cost', compute='_compute_totals', store=True)
total_operation_cost = fields.Float(string='Total Labor / CM Cost', compute='_compute_totals', store=True)
total_unit_cost = fields.Float(string='Estimated Garment Cost (per piece)', compute='_compute_totals', store=True)
@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.bom') or _('BOM-%s') % fields.Date.today().strftime('%Y%m%d')
return super().create(vals_list)
@api.depends('fabric_line_ids.subtotal_cost', 'trim_line_ids.subtotal_cost', 'operation_line_ids.subtotal_cost', 'base_qty')
def _compute_totals(self):
for bom in self:
fab_sum = sum(bom.fabric_line_ids.mapped('subtotal_cost'))
trim_sum = sum(bom.trim_line_ids.mapped('subtotal_cost'))
op_sum = sum(bom.operation_line_ids.mapped('subtotal_cost'))
bom.total_fabric_cost = fab_sum
bom.total_trim_cost = trim_sum
bom.total_operation_cost = op_sum
qty = bom.base_qty or 1.0
bom.total_unit_cost = (fab_sum + trim_sum + op_sum) / qty
def calculate_requirement_for_qty(self, order_qty):
"""Calculates material requirements for an arbitrary production quantity"""
self.ensure_one()
factor = order_qty / (self.base_qty or 1.0)
fabric_req = []
for fab in self.fabric_line_ids:
net_kg = fab.consumption_kg * factor
gross_kg = net_kg * (1.0 + (fab.wastage_pct / 100.0))
fabric_req.append({
'component': fab.component_type,
'fabric_type': fab.fabric_type_id.name if fab.fabric_type_id else 'Fabric',
'net_kg': round(net_kg, 2),
'wastage_pct': fab.wastage_pct,
'gross_kg': round(gross_kg, 2),
'cost': round(gross_kg * fab.cost_per_kg, 2),
})
trims_req = []
for trim in self.trim_line_ids:
net_units = trim.consumption_qty * factor
gross_units = net_units * (1.0 + (trim.wastage_pct / 100.0))
trims_req.append({
'item': trim.item_name or trim.product_id.name,
'item_type': trim.trim_type,
'net_units': round(net_units, 2),
'gross_units': round(gross_units, 2),
'cost': round(gross_units * trim.unit_cost, 2),
})
return {
'order_qty': order_qty,
'fabrics': fabric_req,
'trims': trims_req,
}
class GarmentBomFabric(models.Model):
_name = 'garment.bom.fabric'
_description = 'Garment BOM Fabric Component'
bom_id = fields.Many2one('garment.bom', string='BOM', required=True, ondelete='cascade')
component_type = fields.Selection([
('body', 'Body Panels (Front & Back)'),
('neck_rib', 'Neck Rib / Collar Band'),
('collar', 'Knit / Flat Collar'),
('cuff', 'Sleeve Cuffs'),
('pocket', 'Pocket Panels'),
('placket', 'Placket Facing'),
('hood', 'Hood Panels'),
('lining', 'Lining Fabric'),
('other', 'Other Fabric Part'),
], string='Component Part', default='body', required=True)
fabric_type_id = fields.Many2one('garment.fabric.type', string='Fabric Type')
product_id = fields.Many2one('product.product', string='Raw Fabric Product')
gsm = fields.Integer(string='GSM', default=180)
width = fields.Float(string='Width (Inches)', default=30.0)
# Consumption per garment (Kg)
consumption_kg = fields.Float(string='Net Consumption (Kg/Pc)', required=True, default=0.180, digits=(16, 4))
wastage_pct = fields.Float(string='Cutting Wastage %', default=5.0)
gross_consumption_kg = fields.Float(string='Gross Consumption (Kg/Pc)', compute='_compute_gross_kg', store=True, digits=(16, 4))
cost_per_kg = fields.Float(string='Fabric Cost / Kg', default=320.0)
subtotal_cost = fields.Float(string='Fabric Cost / Pc', compute='_compute_subtotal_cost', store=True)
@api.depends('consumption_kg', 'wastage_pct')
def _compute_gross_kg(self):
for rec in self:
rec.gross_consumption_kg = rec.consumption_kg * (1.0 + (rec.wastage_pct / 100.0))
@api.depends('gross_consumption_kg', 'cost_per_kg')
def _compute_subtotal_cost(self):
for rec in self:
rec.subtotal_cost = rec.gross_consumption_kg * rec.cost_per_kg
class GarmentBomTrim(models.Model):
_name = 'garment.bom.trim'
_description = 'Garment BOM Trims & Accessories'
bom_id = fields.Many2one('garment.bom', string='BOM', required=True, ondelete='cascade')
trim_type = fields.Selection([
('main_label', 'Main Woven / Heat-Transfer Label'),
('size_label', 'Size Label'),
('care_label', 'Wash Care Satin Label'),
('hang_tag', 'Brand Hang Tag'),
('price_tag', 'Retail Price Ticket'),
('polybag', 'Individual Polybag'),
('carton', 'Master Export Carton Box'),
('thread', 'Sewing Thread (Spun Polyester)'),
('button', 'Buttons'),
('zipper', 'Zipper'),
('elastic', 'Elastic Band'),
('drawstring', 'Drawstring / Cord'),
('tissue_paper', 'Tissue Paper / Board'),
('silica_gel', 'Silica Gel Desiccant'),
('other', 'Other Accessory'),
], string='Trim Category', required=True, default='main_label')
item_name = fields.Char(string='Trim Item Name', required=True)
product_id = fields.Many2one('product.product', string='Trims Product')
uom_id = fields.Many2one('uom.uom', string='UoM', default=lambda self: self.env.ref('uom.product_uom_unit', raise_if_not_found=False))
consumption_qty = fields.Float(string='Net Qty / Pc', required=True, default=1.0, digits=(16, 4))
wastage_pct = fields.Float(string='Allowance %', default=3.0)
gross_qty = fields.Float(string='Gross Qty / Pc', compute='_compute_gross_qty', store=True, digits=(16, 4))
unit_cost = fields.Float(string='Unit Cost', default=1.5)
subtotal_cost = fields.Float(string='Cost / Pc', compute='_compute_subtotal_cost', store=True)
@api.depends('consumption_qty', 'wastage_pct')
def _compute_gross_qty(self):
for rec in self:
rec.gross_qty = rec.consumption_qty * (1.0 + (rec.wastage_pct / 100.0))
@api.depends('gross_qty', 'unit_cost')
def _compute_subtotal_cost(self):
for rec in self:
rec.subtotal_cost = rec.gross_qty * rec.unit_cost
class GarmentBomOperation(models.Model):
_name = 'garment.bom.operation'
_description = 'Garment BOM Operations & Routing'
_order = 'sequence, id'
bom_id = fields.Many2one('garment.bom', string='BOM', required=True, ondelete='cascade')
sequence = fields.Integer(string='Sequence', default=10)
operation_name = fields.Char(string='Operation Name', required=True)
department = fields.Selection([
('cutting', 'Cutting Section'),
('sewing', 'Sewing Assembly Line'),
('jobwork', 'External Job Work'),
('washing', 'Washing & Bio-wash'),
('finishing', 'Ironing & Finishing'),
('packing', 'Packing Section'),
], string='Department', default='sewing', required=True)
machine_type = fields.Selection([
('manual', 'Manual / Hand Work'),
('snls', 'Single Needle Lockstitch (SNLS)'),
('overlock_4t', '4-Thread Overlock (4T)'),
('overlock_5t', '5-Thread Overlock (5T)'),
('flatlock', 'Flatlock / Coverstitch'),
('button_attach', 'Button Stitching Machine'),
('button_hole', 'Button Hole Machine'),
('kansai', 'Multi-Needle Kansai'),
('steam_iron', 'Vacuum Steam Press'),
], string='Machine Required', default='overlock_4t')
sam = fields.Float(string='SAM (Minutes)', default=1.5, help='Standard Allowed Minutes')
minute_rate = fields.Float(string='Labor Cost / Minute', default=2.5)
subtotal_cost = fields.Float(string='Cost / Pc', compute='_compute_subtotal_cost', store=True)
@api.depends('sam', 'minute_rate')
def _compute_subtotal_cost(self):
for rec in self:
rec.subtotal_cost = rec.sam * rec.minute_rate

View File

@ -0,0 +1,100 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class GarmentCosting(models.Model):
_name = 'garment.costing'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Garment Pre-Costing & Post-Costing Sheet'
_order = 'name desc'
name = fields.Char(string='Cost Sheet No', required=True, default=lambda self: _('New'), copy=False, tracking=True)
style_id = fields.Many2one('garment.style', string='Garment Style', required=True, tracking=True)
buyer_id = fields.Many2one('res.partner', string='Buyer / Customer', tracking=True)
order_qty = fields.Integer(string='Target Order Quantity (Pcs)', required=True, default=10000)
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
currency_id = fields.Many2one('res.currency', string='Currency', default=lambda self: self.env.company.currency_id)
costing_type = fields.Selection([
('pre_costing', 'Pre-Costing (Quotation / Estimate)'),
('post_costing', 'Post-Costing (Actual Production Variance)'),
], string='Costing Stage', default='pre_costing', required=True, tracking=True)
# 1. Raw Materials (Fabric + Dyeing)
fabric_cost_per_pc = fields.Float(string='Yarn / Raw Fabric Cost / Pc', default=65.0, digits=(16, 2))
dyeing_finishing_cost_per_pc = fields.Float(string='Dyeing & Bleaching / Pc', default=12.0, digits=(16, 2))
# 2. Trims & Accessories
trims_accessories_cost_per_pc = fields.Float(string='Trims & Labels / Pc', default=8.5, digits=(16, 2))
# 3. Value Addition
printing_embroidery_cost_per_pc = fields.Float(string='Chest Print / Embroidery / Pc', default=14.0, digits=(16, 2))
washing_cost_per_pc = fields.Float(string='Garment Bio-Washing / Pc', default=5.0, digits=(16, 2))
# 4. Manufacturing Labor (CM - Cut & Make)
cutting_cost_per_pc = fields.Float(string='Cutting Labor / Pc', default=4.0, digits=(16, 2))
sewing_cm_cost_per_pc = fields.Float(string='Sewing Assembly CM / Pc', default=28.0, digits=(16, 2), help='Standard sewing labor cost')
# 5. Packaging & Forwarding
packing_carton_cost_per_pc = fields.Float(string='Polybags & Master Cartons / Pc', default=6.5, digits=(16, 2))
commercial_freight_cost_per_pc = fields.Float(string='Local Freight & Port Handling / Pc', default=3.0, digits=(16, 2))
# 6. Factory Overheads
factory_overhead_pct = fields.Float(string='Factory Overheads %', default=8.0)
factory_overhead_cost = fields.Float(string='Overheads Cost / Pc', compute='_compute_totals', store=True)
# Total Costs
total_cost_per_pc = fields.Float(string='Total Garment Cost / Pc', compute='_compute_totals', store=True)
total_cost_per_dozen = fields.Float(string='Total Cost / Dozen', compute='_compute_totals', store=True)
total_order_cost = fields.Float(string='Total Order Cost', compute='_compute_totals', store=True)
# Commercial Pricing & Margins
target_selling_price = fields.Float(string='Target Selling Price / Pc', default=180.0, required=True, tracking=True)
gross_margin_amount = fields.Float(string='Gross Margin / Pc', compute='_compute_margins', store=True)
gross_margin_pct = fields.Float(string='Gross Margin %', compute='_compute_margins', store=True)
total_expected_profit = fields.Float(string='Total Expected Profit', compute='_compute_margins', store=True)
notes = fields.Text(string='Costing Notes & Assumptions')
@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.costing') or _('COST/%s') % fields.Date.today().strftime('%Y%m%d')
return super(GarmentCosting, self).create(vals_list)
@api.depends('fabric_cost_per_pc', 'dyeing_finishing_cost_per_pc', 'trims_accessories_cost_per_pc',
'printing_embroidery_cost_per_pc', 'washing_cost_per_pc', 'cutting_cost_per_pc',
'sewing_cm_cost_per_pc', 'packing_carton_cost_per_pc', 'commercial_freight_cost_per_pc',
'factory_overhead_pct', 'order_qty')
def _compute_totals(self):
for rec in self:
direct_cost = (rec.fabric_cost_per_pc + rec.dyeing_finishing_cost_per_pc +
rec.trims_accessories_cost_per_pc + rec.printing_embroidery_cost_per_pc +
rec.washing_cost_per_pc + rec.cutting_cost_per_pc +
rec.sewing_cm_cost_per_pc + rec.packing_carton_cost_per_pc +
rec.commercial_freight_cost_per_pc)
rec.factory_overhead_cost = round(direct_cost * (rec.factory_overhead_pct / 100.0), 2)
tot_pc = round(direct_cost + rec.factory_overhead_cost, 2)
rec.total_cost_per_pc = tot_pc
rec.total_cost_per_dozen = round(tot_pc * 12.0, 2)
rec.total_order_cost = round(tot_pc * (rec.order_qty or 1), 2)
@api.depends('total_cost_per_pc', 'target_selling_price', 'order_qty')
def _compute_margins(self):
for rec in self:
margin = rec.target_selling_price - rec.total_cost_per_pc
rec.gross_margin_amount = round(margin, 2)
if rec.target_selling_price > 0:
rec.gross_margin_pct = round((margin / rec.target_selling_price) * 100.0, 2)
else:
rec.gross_margin_pct = 0.0
rec.total_expected_profit = round(margin * (rec.order_qty or 1), 2)
def action_load_from_bom(self):
"""Pre-populates raw material and labor costs from the active Garment BOM"""
self.ensure_one()
bom = self.env['garment.bom'].search([('style_id', '=', self.style_id.id)], limit=1)
if bom:
self.fabric_cost_per_pc = bom.total_fabric_cost
self.trims_accessories_cost_per_pc = bom.total_trim_cost
self.sewing_cm_cost_per_pc = bom.total_operation_cost

View File

@ -0,0 +1,72 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class GarmentDashboard(models.Model):
_name = 'garment.dashboard'
_description = 'Garment Manufacturing Real-Time KPI Dashboard'
name = fields.Char(string='Dashboard', default='Tiruppur Factory Control Room')
# Executive KPIs
total_sales_orders = fields.Integer(string='Active Garment Orders', compute='_compute_kpis')
total_ordered_pieces = fields.Integer(string='Total Booked Pieces', compute='_compute_kpis')
active_production_plans = fields.Integer(string='Production Orders in Progress', compute='_compute_kpis')
avg_production_progress = fields.Float(string='Average Production Progress %', compute='_compute_kpis')
# Shop Floor & Sewing KPIs
total_sewing_lines = fields.Integer(string='Active Sewing Lines', compute='_compute_kpis')
today_target_pieces = fields.Integer(string="Today's Sewing Target", compute='_compute_kpis')
today_actual_pieces = fields.Integer(string="Today's Actual Output", compute='_compute_kpis')
avg_line_efficiency = fields.Float(string='Factory Efficiency %', compute='_compute_kpis')
today_defects = fields.Integer(string="Today's Alteration Count", compute='_compute_kpis')
# Material & Inventory KPIs
total_fabric_stock_kg = fields.Float(string='Fabric Rolls in Stock (Kg)', compute='_compute_kpis')
total_approved_rolls = fields.Integer(string='QC Approved Rolls', compute='_compute_kpis')
total_finished_goods_pcs = fields.Integer(string='Finished Goods Stock (Pcs)', compute='_compute_kpis')
# Operations & Subcontracting
pending_jobwork_dc = fields.Integer(string='Pending Job Work Challans', compute='_compute_kpis')
pending_quality_inspections = fields.Integer(string='Pending Quality Inspections', compute='_compute_kpis')
active_rework_orders = fields.Integer(string='Open Rework Orders', compute='_compute_kpis')
pending_dispatch_cartons = fields.Integer(string='Cartons Ready for Dispatch', compute='_compute_kpis')
def _compute_kpis(self):
for rec in self:
# Sales
orders = self.env['sale.order'].search([('is_garment_order', '=', True), ('state', 'in', ['sale', 'done'])])
rec.total_sales_orders = len(orders)
rec.total_ordered_pieces = sum(orders.mapped('total_garment_qty'))
# Production
plans = self.env['garment.production.plan'].search([('state', 'in', ['planned', 'in_progress'])])
rec.active_production_plans = len(plans)
rec.avg_production_progress = round(sum(plans.mapped('progress_pct')) / (len(plans) or 1), 1)
# Sewing Lines & Today's Hourly
lines = self.env['garment.sewing.line'].search([('active', '=', True)])
rec.total_sewing_lines = len(lines)
today_logs = self.env['garment.sewing.hourly'].search([('date', '=', fields.Date.today())])
rec.today_target_pieces = sum(today_logs.mapped('total_target'))
rec.today_actual_pieces = sum(today_logs.mapped('total_actual'))
rec.today_defects = sum(today_logs.mapped('total_defects'))
if today_logs:
rec.avg_line_efficiency = round(sum(today_logs.mapped('efficiency_pct')) / len(today_logs), 1)
else:
rec.avg_line_efficiency = 0.0
# Fabric & FG
rolls = self.env['garment.fabric.roll'].search([('status', 'in', ['received', 'inspected', 'approved', 'issued'])])
rec.total_fabric_stock_kg = round(sum(rolls.mapped('remaining_weight')), 1)
rec.total_approved_rolls = self.env['garment.fabric.roll'].search_count([('status', '=', 'approved')])
fg = self.env['garment.finished.goods'].search([])
rec.total_finished_goods_pcs = sum(fg.mapped('on_hand_qty'))
# Job Work & QC
rec.pending_jobwork_dc = self.env['garment.jobwork.order'].search_count([('state', 'in', ['sent', 'partial'])])
rec.pending_quality_inspections = self.env['garment.quality.inspection'].search_count([('verdict', '=', 'rework_needed')])
rec.active_rework_orders = self.env['garment.rework.order'].search_count([('state', 'in', ['assigned', 'in_rework', 'ready_recheck'])])
# Packing
rec.pending_dispatch_cartons = self.env['garment.carton'].search_count([('dispatch_id', '=', False)])

View File

@ -0,0 +1,172 @@
# -*- 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')

View File

@ -0,0 +1,136 @@
# -*- 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)

View File

@ -0,0 +1,175 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import UserError
class GarmentMrpRequirement(models.Model):
_name = 'garment.mrp.requirement'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Garment Material Requirement Planning (MRP)'
_order = 'name desc'
name = fields.Char(string='MRP Requisition No', required=True, default=lambda self: _('New'), copy=False)
production_plan_id = fields.Many2one('garment.production.plan', string='Production Plan', required=True, tracking=True)
style_id = fields.Many2one('garment.style', string='Garment Style', related='production_plan_id.style_id', readonly=True)
bom_id = fields.Many2one('garment.bom', string='Garment BOM', required=True)
order_qty = fields.Integer(string='Production Order Qty (Pcs)', required=True, default=1000)
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
line_ids = fields.One2many('garment.mrp.requirement.line', 'mrp_id', string='Material Shortage & Requirement Lines')
total_shortage_cost = fields.Float(string='Total Shortage Purchase Cost', compute='_compute_totals', store=True)
state = fields.Selection([
('draft', 'Draft'),
('calculated', 'Requirements Calculated'),
('rfq_created', 'Purchase RFQs Generated'),
('closed', 'Completed / Closed'),
], string='Status', default='draft', tracking=True)
purchase_order_ids = fields.Many2many('purchase.order', string='Generated Purchase Orders')
purchase_count = fields.Integer(string='Purchase Orders', compute='_compute_purchase_count')
@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.mrp.requirement') or _('MRP/%s') % fields.Date.today().strftime('%Y%m%d')
return super().create(vals_list)
@api.depends('line_ids.shortage_cost')
def _compute_totals(self):
for mrp in self:
mrp.total_shortage_cost = sum(mrp.line_ids.mapped('shortage_cost'))
@api.depends('purchase_order_ids')
def _compute_purchase_count(self):
for mrp in self:
mrp.purchase_count = len(mrp.purchase_order_ids)
def action_compute_requirements(self):
self.ensure_one()
if not self.bom_id:
raise UserError(_("Please assign a BOM."))
# Clear existing
self.line_ids.unlink()
calc = self.bom_id.calculate_requirement_for_qty(self.order_qty)
lines = []
# Fabric Requirements
for fab in self.bom_id.fabric_line_ids:
factor = self.order_qty / (self.bom_id.base_qty or 1.0)
req_kg = round(fab.gross_consumption_kg * factor, 2)
product = fab.product_id
avail_stock = product.qty_available if product else 0.0
shortage = max(0.0, req_kg - avail_stock)
lines.append((0, 0, {
'material_type': 'fabric',
'name': f"{fab.fabric_type_id.name or 'Fabric'} ({fab.component_type.upper()})",
'product_id': product.id if product else False,
'required_qty': req_kg,
'uom_name': 'Kg',
'available_qty': avail_stock,
'shortage_qty': shortage,
'to_purchase_qty': shortage,
'unit_price': fab.cost_per_kg or 320.0,
}))
# Trims Requirements
for trim in self.bom_id.trim_line_ids:
factor = self.order_qty / (self.bom_id.base_qty or 1.0)
req_qty = round(trim.gross_qty * factor, 2)
product = trim.product_id
avail_stock = product.qty_available if product else 0.0
shortage = max(0.0, req_qty - avail_stock)
lines.append((0, 0, {
'material_type': 'trim',
'name': trim.item_name,
'product_id': product.id if product else False,
'required_qty': req_qty,
'uom_name': trim.uom_id.name if trim.uom_id else 'Units',
'available_qty': avail_stock,
'shortage_qty': shortage,
'to_purchase_qty': shortage,
'unit_price': trim.unit_cost or 1.5,
}))
self.write({
'line_ids': lines,
'state': 'calculated',
})
def action_generate_purchase_rfqs(self):
"""Generates draft Purchase Orders for all shortage materials"""
self.ensure_one()
shortage_lines = self.line_ids.filtered(lambda l: l.to_purchase_qty > 0)
if not shortage_lines:
raise UserError(_("No material shortages found to purchase."))
# Find or create a default vendor if product doesn't have one
default_vendor = self.env['res.partner'].search([('supplier_rank', '>', 0)], limit=1)
if not default_vendor:
default_vendor = self.env['res.partner'].create({
'name': 'Garment Raw Materials Supplier',
'supplier_rank': 1,
})
po_lines = []
for line in shortage_lines:
product = line.product_id
if not product:
product = self.env['product.product'].create({
'name': line.name,
'type': 'consu',
})
line.product_id = product.id
po_lines.append((0, 0, {
'product_id': product.id,
'name': f"Requirement for {self.production_plan_id.name}: {line.name}",
'product_qty': line.to_purchase_qty,
'price_unit': line.unit_price,
'date_planned': fields.Datetime.now(),
}))
po = self.env['purchase.order'].create({
'partner_id': default_vendor.id,
'origin': f"MRP: {self.name} / {self.production_plan_id.name}",
'order_line': po_lines,
})
self.write({
'purchase_order_ids': [(4, po.id)],
'state': 'rfq_created',
})
return {
'name': _('Generated Purchase Order'),
'type': 'ir.actions.act_window',
'res_model': 'purchase.order',
'res_id': po.id,
'view_mode': 'form',
}
class GarmentMrpRequirementLine(models.Model):
_name = 'garment.mrp.requirement.line'
_description = 'Garment MRP Material Shortage Line'
mrp_id = fields.Many2one('garment.mrp.requirement', string='MRP', required=True, ondelete='cascade')
material_type = fields.Selection([
('fabric', 'Fabric (Kg)'),
('trim', 'Trims & Accessories'),
('packaging', 'Packaging Material'),
], string='Type', default='fabric')
name = fields.Char(string='Material Description', required=True)
product_id = fields.Many2one('product.product', string='Inventory Product')
uom_name = fields.Char(string='UoM', default='Kg')
required_qty = fields.Float(string='Required Quantity', default=0.0)
available_qty = fields.Float(string='Available Stock', default=0.0)
shortage_qty = fields.Float(string='Stock Shortage', default=0.0)
to_purchase_qty = fields.Float(string='To Purchase Qty', default=0.0)
unit_price = fields.Float(string='Est. Unit Price', default=0.0)
shortage_cost = fields.Float(string='Est. Shortage Cost', compute='_compute_shortage_cost', store=True)
@api.depends('to_purchase_qty', 'unit_price')
def _compute_shortage_cost(self):
for line in self:
line.shortage_cost = line.to_purchase_qty * line.unit_price

View File

@ -0,0 +1,143 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import UserError
class GarmentPackingPlan(models.Model):
_name = 'garment.packing.plan'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Garment Export & Retail Packing Management'
_order = 'name desc'
name = fields.Char(string='Packing Plan No', required=True, default=lambda self: _('New'), copy=False, tracking=True)
production_plan_id = fields.Many2one('garment.production.plan', string='Production Order', required=True, tracking=True)
sale_order_id = fields.Many2one('sale.order', string='Sales Order', tracking=True)
style_id = fields.Many2one('garment.style', string='Garment Style', required=True, tracking=True)
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
packing_type = fields.Selection([
('solid_color_solid_size', 'Solid Color / Solid Size (Single Variant per Box)'),
('solid_color_assorted_size', 'Solid Color / Assorted Size (Ratio Pack)'),
('assorted_color_assorted_size', 'Assorted Color / Assorted Size (Pre-Pack)'),
], string='Carton Packing Method', default='solid_color_assorted_size', required=True, tracking=True)
target_pack_qty = fields.Integer(string='Target Pack Pieces', default=1000, required=True)
total_packed_qty = fields.Integer(string='Total Packed Pieces', compute='_compute_carton_totals', store=True)
total_cartons = fields.Integer(string='Total Cartons', compute='_compute_carton_totals', store=True)
total_cbm = fields.Float(string='Total Shipment CBM (m³)', compute='_compute_carton_totals', store=True)
total_gross_weight = fields.Float(string='Total Gross Weight (Kg)', compute='_compute_carton_totals', store=True)
carton_ids = fields.One2many('garment.carton', 'packing_plan_id', string='Master Cartons')
state = fields.Selection([
('draft', 'Draft'),
('in_packing', 'Packing In Progress'),
('completed', 'Packed & Transferred to FG'),
], string='Status', default='draft', tracking=True, required=True)
@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.packing.plan') or _('PACK/%s') % fields.Date.today().strftime('%Y%m%d')
return super(GarmentPackingPlan, self).create(vals_list)
@api.depends('carton_ids.total_pieces', 'carton_ids.cbm', 'carton_ids.gross_weight_kg')
def _compute_carton_totals(self):
for plan in self:
plan.total_packed_qty = sum(plan.carton_ids.mapped('total_pieces'))
plan.total_cartons = len(plan.carton_ids)
plan.total_cbm = round(sum(plan.carton_ids.mapped('cbm')), 3)
plan.total_gross_weight = round(sum(plan.carton_ids.mapped('gross_weight_kg')), 2)
def action_start_packing(self):
self.write({'state': 'in_packing'})
def action_transfer_to_finished_goods(self):
"""Creates or updates finished goods stock records from packed cartons"""
self.ensure_one()
if not self.carton_ids:
raise UserError(_("No cartons found in this packing plan."))
FG = self.env['garment.finished.goods']
for carton in self.carton_ids:
for line in carton.line_ids:
existing_fg = FG.search([
('style_id', '=', self.style_id.id),
('color_id', '=', line.color_id.id),
('size_id', '=', line.size_id.id),
], limit=1)
if existing_fg:
existing_fg.write({'on_hand_qty': existing_fg.on_hand_qty + line.quantity})
else:
FG.create({
'style_id': self.style_id.id,
('color_id'): line.color_id.id,
('size_id'): line.size_id.id,
'on_hand_qty': line.quantity,
})
self.write({'state': 'completed'})
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Finished Goods Stock Updated'),
'message': _('Successfully transferred %d packed garments into Finished Goods Inventory.') % self.total_packed_qty,
'sticky': False,
'type': 'success',
}
}
class GarmentCarton(models.Model):
_name = 'garment.carton'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Master Export Carton'
_order = 'carton_no, name'
name = fields.Char(string='Carton ID', required=True, default=lambda self: _('New'), copy=False)
carton_no = fields.Integer(string='Carton #', default=1, required=True)
packing_plan_id = fields.Many2one('garment.packing.plan', string='Packing Plan', required=True, ondelete='cascade')
barcode = fields.Char(string='Carton Barcode / Shipping QR', index=True, copy=False)
style_id = fields.Many2one('garment.style', string='Garment Style', related='packing_plan_id.style_id', readonly=True)
# Dimensions & Weights
length_cm = fields.Float(string='Length (cm)', default=60.0)
width_cm = fields.Float(string='Width (cm)', default=40.0)
height_cm = fields.Float(string='Height (cm)', default=30.0)
cbm = fields.Float(string='Carton CBM (m³)', compute='_compute_cbm', store=True)
net_weight_kg = fields.Float(string='Net Weight (Kg)', default=12.5)
gross_weight_kg = fields.Float(string='Gross Weight (Kg)', default=14.0)
line_ids = fields.One2many('garment.carton.line', 'carton_id', string='Carton Garment Breakdown')
total_pieces = fields.Integer(string='Total Pieces in Box', compute='_compute_pieces', store=True)
dispatch_id = fields.Many2one('garment.dispatch', string='Dispatch Order')
@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.carton') or _('CTN-%s') % fields.Date.today().strftime('%Y%m%d')
if not vals.get('barcode'):
vals['barcode'] = vals['name']
return super(GarmentCarton, self).create(vals_list)
@api.depends('length_cm', 'width_cm', 'height_cm')
def _compute_cbm(self):
for ctn in self:
ctn.cbm = round((ctn.length_cm * ctn.width_cm * ctn.height_cm) / 1000000.0, 4)
@api.depends('line_ids.quantity')
def _compute_pieces(self):
for ctn in self:
ctn.total_pieces = sum(ctn.line_ids.mapped('quantity'))
class GarmentCartonLine(models.Model):
_name = 'garment.carton.line'
_description = 'Carton Contents Breakdown Line'
carton_id = fields.Many2one('garment.carton', string='Carton', required=True, ondelete='cascade')
color_id = fields.Many2one('garment.color', string='Color', required=True)
size_id = fields.Many2one('garment.size', string='Size', required=True)
quantity = fields.Integer(string='Pieces in Carton', default=10, required=True)

View File

@ -0,0 +1,265 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import UserError
class GarmentProductionPlan(models.Model):
_name = 'garment.production.plan'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Garment Production Master Plan'
_order = 'name desc'
name = fields.Char(string='Plan Number', required=True, default=lambda self: _('New'), copy=False, tracking=True)
sale_order_id = fields.Many2one('sale.order', string='Sales Order', tracking=True)
style_id = fields.Many2one('garment.style', string='Garment Style', required=True, tracking=True)
bom_id = fields.Many2one('garment.bom', string='Garment BOM', tracking=True)
company_id = fields.Many2one('res.company', string='Company', default=lambda self: self.env.company)
# Dates & Scheduling
date_start = fields.Date(string='Production Start Date', default=fields.Date.today, tracking=True)
date_delivery = fields.Date(string='Target Delivery Date', tracking=True)
priority = fields.Selection([
('0', 'Low'),
('1', 'Normal'),
('2', 'High'),
('3', 'Urgent'),
], string='Priority', default='1', tracking=True)
# State Workflow
state = fields.Selection([
('draft', 'Draft'),
('planned', 'Planned / Scheduled'),
('in_progress', 'In Production'),
('completed', 'Completed'),
('cancelled', 'Cancelled'),
], string='Status', default='draft', tracking=True, required=True)
# Quantitative Progress Tracking
planned_qty = fields.Integer(string='Planned Quantity (Pcs)', required=True, default=1000, tracking=True)
cut_qty = fields.Integer(string='Cut Quantity', compute='_compute_stage_quantities', store=True)
sewn_qty = fields.Integer(string='Sewn Quantity', compute='_compute_stage_quantities', store=True)
packed_qty = fields.Integer(string='Packed Quantity', compute='_compute_stage_quantities', store=True)
dispatched_qty = fields.Integer(string='Dispatched Quantity', compute='_compute_stage_quantities', store=True)
progress_pct = fields.Float(string='Production Progress %', compute='_compute_progress_pct', store=True)
# Line Breakdown (Color x Size breakdown for this production order)
line_ids = fields.One2many('garment.production.plan.line', 'plan_id', string='Color/Size Production Targets')
# Child Records
cutting_plan_ids = fields.One2many('garment.cutting.plan', 'production_plan_id', string='Cutting Orders')
bundle_ids = fields.One2many('garment.bundle', 'production_plan_id', string='Bundles')
jobwork_order_ids = fields.One2many('garment.jobwork.order', 'production_plan_id', string='Job Work Orders')
qc_inspection_ids = fields.One2many('garment.quality.inspection', 'production_plan_id', string='QC Inspections')
packing_plan_ids = fields.One2many('garment.packing.plan', 'production_plan_id', string='Packing Plans')
mrp_requirement_ids = fields.One2many('garment.mrp.requirement', 'production_plan_id', string='MRP Requirements')
# Smart Button Counts
cutting_count = fields.Integer(string='Cutting Count', compute='_compute_counts')
bundle_count = fields.Integer(string='Bundle Count', compute='_compute_counts')
jobwork_count = fields.Integer(string='Job Work Count', compute='_compute_counts')
qc_count = fields.Integer(string='QC Count', compute='_compute_counts')
packing_count = fields.Integer(string='Packing Count', compute='_compute_counts')
notes = fields.Html(string='Production Notes')
@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.production.plan') or _('PP/%s') % fields.Date.today().strftime('%Y%m%d')
plans = super().create(vals_list)
for plan in plans:
if plan.sale_order_id and not plan.line_ids:
plan._populate_lines_from_sale_order()
return plans
def _populate_lines_from_sale_order(self):
self.ensure_one()
if not self.sale_order_id:
return
lines = []
for m_line in self.sale_order_id.order_matrix_line_ids:
lines.append((0, 0, {
'color_id': m_line.color_id.id,
'size_id': m_line.size_id.id,
'planned_qty': m_line.quantity,
}))
if lines:
self.write({'line_ids': lines})
@api.onchange('style_id')
def _onchange_style_id(self):
if self.style_id:
bom = self.env['garment.bom'].search([('style_id', '=', self.style_id.id)], limit=1)
if bom:
self.bom_id = bom.id
@api.depends('cutting_plan_ids.actual_cut_qty', 'packing_plan_ids.total_packed_qty')
def _compute_stage_quantities(self):
for plan in self:
plan.cut_qty = sum(plan.cutting_plan_ids.mapped('actual_cut_qty'))
plan.packed_qty = sum(plan.packing_plan_ids.mapped('total_packed_qty'))
# Sewn qty from bundles completed in sewing
sewn_bundles = plan.bundle_ids.filtered(lambda b: b.stage in ['quality', 'packing', 'completed'])
plan.sewn_qty = sum(sewn_bundles.mapped('quantity'))
# Dispatched qty from dispatch orders
dispatches = self.env['garment.dispatch'].search([('production_plan_id', '=', plan.id), ('state', '=', 'dispatched')])
plan.dispatched_qty = sum(dispatches.mapped('total_pieces'))
@api.depends('planned_qty', 'packed_qty')
def _compute_progress_pct(self):
for plan in self:
if plan.planned_qty > 0:
plan.progress_pct = min(100.0, round((plan.packed_qty / plan.planned_qty) * 100.0, 1))
else:
plan.progress_pct = 0.0
@api.depends('cutting_plan_ids', 'bundle_ids', 'jobwork_order_ids', 'qc_inspection_ids', 'packing_plan_ids')
def _compute_counts(self):
for plan in self:
plan.cutting_count = len(plan.cutting_plan_ids)
plan.bundle_count = len(plan.bundle_ids)
plan.jobwork_count = len(plan.jobwork_order_ids)
plan.qc_count = len(plan.qc_inspection_ids)
plan.packing_count = len(plan.packing_plan_ids)
# Workflow Actions
def action_plan(self):
self.write({'state': 'planned'})
def action_start(self):
self.write({'state': 'in_progress'})
def action_complete(self):
self.write({'state': 'completed'})
def action_cancel(self):
self.write({'state': 'cancelled'})
def action_reset_to_draft(self):
self.write({'state': 'draft'})
def action_create_cutting_plan(self):
self.ensure_one()
cut_plan = self.env['garment.cutting.plan'].create({
'production_plan_id': self.id,
'style_id': self.style_id.id,
'planned_cut_qty': self.planned_qty,
'color_id': self.line_ids[0].color_id.id if self.line_ids else False,
})
return {
'name': _('Cutting Order'),
'type': 'ir.actions.act_window',
'res_model': 'garment.cutting.plan',
'res_id': cut_plan.id,
'view_mode': 'form',
}
def action_create_mrp_requirement(self):
self.ensure_one()
if not self.bom_id:
raise UserError(_("Please assign a BOM to calculate Material Requirements."))
req = self.env['garment.mrp.requirement'].create({
'production_plan_id': self.id,
'style_id': self.style_id.id,
'bom_id': self.bom_id.id,
'order_qty': self.planned_qty,
})
req.action_compute_requirements()
return {
'name': _('Material Requirements Calculation'),
'type': 'ir.actions.act_window',
'res_model': 'garment.mrp.requirement',
'res_id': req.id,
'view_mode': 'form',
}
def action_create_packing_plan(self):
self.ensure_one()
pack = self.env['garment.packing.plan'].create({
'production_plan_id': self.id,
'sale_order_id': self.sale_order_id.id if self.sale_order_id else False,
'style_id': self.style_id.id,
'target_pack_qty': self.planned_qty,
})
return {
'name': _('Packing Plan'),
'type': 'ir.actions.act_window',
'res_model': 'garment.packing.plan',
'res_id': pack.id,
'view_mode': 'form',
}
# Smart Button navigation actions
def action_view_cutting_plans(self):
self.ensure_one()
return {
'name': _('Cutting Orders (%s)') % self.name,
'type': 'ir.actions.act_window',
'res_model': 'garment.cutting.plan',
'view_mode': 'list,form',
'domain': [('production_plan_id', '=', self.id)],
'context': {'default_production_plan_id': self.id, 'default_style_id': self.style_id.id},
}
def action_view_bundles(self):
self.ensure_one()
return {
'name': _('Production Bundles (%s)') % self.name,
'type': 'ir.actions.act_window',
'res_model': 'garment.bundle',
'view_mode': 'list,form',
'domain': [('production_plan_id', '=', self.id)],
'context': {'default_production_plan_id': self.id},
}
def action_view_jobwork(self):
self.ensure_one()
return {
'name': _('Job Work Orders (%s)') % self.name,
'type': 'ir.actions.act_window',
'res_model': 'garment.jobwork.order',
'view_mode': 'list,form',
'domain': [('production_plan_id', '=', self.id)],
'context': {'default_production_plan_id': self.id},
}
def action_view_qc(self):
self.ensure_one()
return {
'name': _('Quality Inspections (%s)') % self.name,
'type': 'ir.actions.act_window',
'res_model': 'garment.quality.inspection',
'view_mode': 'list,form',
'domain': [('production_plan_id', '=', self.id)],
'context': {'default_production_plan_id': self.id},
}
def action_view_packing(self):
self.ensure_one()
return {
'name': _('Packing Plans (%s)') % self.name,
'type': 'ir.actions.act_window',
'res_model': 'garment.packing.plan',
'view_mode': 'list,form',
'domain': [('production_plan_id', '=', self.id)],
'context': {'default_production_plan_id': self.id},
}
class GarmentProductionPlanLine(models.Model):
_name = 'garment.production.plan.line'
_description = 'Production Target Line by Color & Size'
_order = 'color_id, size_id'
plan_id = fields.Many2one('garment.production.plan', string='Production Plan', required=True, ondelete='cascade')
color_id = fields.Many2one('garment.color', string='Color', required=True)
size_id = fields.Many2one('garment.size', string='Size', required=True)
planned_qty = fields.Integer(string='Planned Pcs', required=True, default=0)
cut_qty = fields.Integer(string='Cut Pcs', default=0)
sewn_qty = fields.Integer(string='Sewn Pcs', default=0)
packed_qty = fields.Integer(string='Packed Pcs', default=0)
variance = fields.Integer(string='Balance to Pack', compute='_compute_variance', store=True)
@api.depends('planned_qty', 'packed_qty')
def _compute_variance(self):
for line in self:
line.variance = line.planned_qty - line.packed_qty

View File

@ -0,0 +1,124 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class GarmentDefectType(models.Model):
_name = 'garment.defect.type'
_description = 'Garment Quality Defect Category'
_order = 'category, name'
name = fields.Char(string='Defect Name', required=True)
code = fields.Char(string='Defect Code', required=True)
category = fields.Selection([
('stitching', 'Stitching / Sewing Defect'),
('fabric', 'Fabric / Yarn Flaw'),
('measurement', 'Measurement / Spec Variance'),
('print_embroidery', 'Print / Embroidery Defect'),
('finishing_packing', 'Finishing / Staining / Packing'),
], string='Category', default='stitching', required=True)
severity = fields.Selection([
('minor', 'Minor Defect'),
('major', 'Major Defect'),
('critical', 'Critical (Must Reject)'),
], string='Defect Severity', default='major')
active = fields.Boolean(default=True)
class GarmentQualityInspection(models.Model):
_name = 'garment.quality.inspection'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Garment Quality Control Inspection'
_order = 'name desc'
name = fields.Char(string='Inspection Number', required=True, default=lambda self: _('New'), copy=False, tracking=True)
inspection_stage = fields.Selection([
('fabric', '1. Fabric Inspection'),
('cutting', '2. Cutting Section QC'),
('sewing_inline', '3. Sewing Inline Roaming QC'),
('sewing_endline', '4. Sewing Endline 100% Checking'),
('jobwork', '5. Job Work Inward QC'),
('final_aql', '6. Final AQL 2.5 Pre-Shipment Audit'),
], string='Inspection Stage', default='sewing_endline', required=True, tracking=True)
production_plan_id = fields.Many2one('garment.production.plan', string='Production Order', tracking=True)
style_id = fields.Many2one('garment.style', string='Garment Style', tracking=True)
color_id = fields.Many2one('garment.color', string='Color')
size_id = fields.Many2one('garment.size', string='Size')
bundle_id = fields.Many2one('garment.bundle', string='Bundle Ref')
sewing_line_id = fields.Many2one('garment.sewing.line', string='Sewing Line')
inspector_id = fields.Many2one('res.users', string='QA Inspector', default=lambda self: self.env.user, tracking=True)
date_inspected = fields.Date(string='Inspection Date', default=fields.Date.today, tracking=True)
# Quantities & AQL
total_lot_qty = fields.Integer(string='Lot / Batch Quantity', default=100)
sample_size = fields.Integer(string='Sample Quantity Checked', default=20, required=True)
passed_qty = fields.Integer(string='Passed Quantity', default=0, tracking=True)
failed_qty = fields.Integer(string='Defective Quantity', default=0, tracking=True)
rework_qty = fields.Integer(string='Sent for Rework', default=0)
scrapped_qty = fields.Integer(string='Permanently Scrapped', default=0)
defect_line_ids = fields.One2many('garment.quality.defect.line', 'inspection_id', string='Defect Details')
verdict = fields.Selection([
('passed', 'Quality Approved (Pass)'),
('rework_needed', 'Rework Required'),
('rejected', 'Completely Rejected / Scrapped'),
], string='Final Verdict', default='passed', tracking=True, required=True)
rework_order_ids = fields.One2many('garment.rework.order', 'inspection_id', string='Linked Rework Orders')
rework_count = fields.Integer(string='Rework Orders', compute='_compute_rework_count')
remarks = fields.Text(string='Inspector Notes')
@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.quality.inspection') or _('QC/%s') % fields.Date.today().strftime('%Y%m%d')
return super(GarmentQualityInspection, self).create(vals_list)
@api.depends('rework_order_ids')
def _compute_rework_count(self):
for rec in self:
rec.rework_count = len(rec.rework_order_ids)
def action_pass(self):
self.write({'verdict': 'passed'})
def action_trigger_rework(self):
"""Creates an automatic Rework Order for failed pieces"""
self.ensure_one()
rework_qty = self.rework_qty or self.failed_qty or 1
rework = self.env['garment.rework.order'].create({
'inspection_id': self.id,
'production_plan_id': self.production_plan_id.id if self.production_plan_id else False,
'style_id': self.style_id.id if self.style_id else False,
'color_id': self.color_id.id if self.color_id else False,
'size_id': self.size_id.id if self.size_id else False,
'bundle_id': self.bundle_id.id if self.bundle_id else False,
'rework_qty': rework_qty,
'defect_description': self.remarks or 'Defects identified during quality inspection',
'assigned_dept': 'sewing',
})
self.write({'verdict': 'rework_needed', 'rework_qty': rework_qty})
return {
'name': _('Rework Order'),
'type': 'ir.actions.act_window',
'res_model': 'garment.rework.order',
'res_id': rework.id,
'view_mode': 'form',
}
class GarmentQualityDefectLine(models.Model):
_name = 'garment.quality.defect.line'
_description = 'Quality Defect Occurrence Line'
inspection_id = fields.Many2one('garment.quality.inspection', string='Inspection', required=True, ondelete='cascade')
defect_type_id = fields.Many2one('garment.defect.type', string='Defect Type', required=True)
category = fields.Selection(related='defect_type_id.category', readonly=True)
defect_count = fields.Integer(string='Defect Count', default=1, required=True)
action_type = fields.Selection([
('rework', 'Alteration / Rework'),
('scrap', 'Permanent Rejection / Scrap'),
], string='Action Required', default='rework')
notes = fields.Char(string='Defect Location / Remarks')

View File

@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
class GarmentReworkOrder(models.Model):
_name = 'garment.rework.order'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Garment Quality Rework Order'
_order = 'name desc'
name = fields.Char(string='Rework Order No', required=True, default=lambda self: _('New'), copy=False, tracking=True)
inspection_id = fields.Many2one('garment.quality.inspection', string='Originating QC Inspection', required=True, tracking=True)
production_plan_id = fields.Many2one('garment.production.plan', string='Production Plan', tracking=True)
style_id = fields.Many2one('garment.style', string='Style', tracking=True)
color_id = fields.Many2one('garment.color', string='Color')
size_id = fields.Many2one('garment.size', string='Size')
bundle_id = fields.Many2one('garment.bundle', string='Bundle Ref')
# Quantities
rework_qty = fields.Integer(string='Rework Quantity (Pcs)', required=True, default=1, tracking=True)
passed_rework_qty = fields.Integer(string='Passed After Rework', default=0)
scrapped_rework_qty = fields.Integer(string='Unrepairable / Scrapped', default=0)
# Assignment
assigned_dept = fields.Selection([
('sewing', 'Sewing Floor Alteration'),
('cutting', 'Cutting Panel Replacement'),
('finishing', 'Spot Cleaning / Ironing'),
('jobwork', 'Job Worker Re-processing'),
], string='Responsible Section', default='sewing', required=True, tracking=True)
assigned_operator_id = fields.Many2one('res.users', string='Assigned Operator / Tailor', tracking=True)
date_assigned = fields.Date(string='Date Assigned', default=fields.Date.today)
date_completed = fields.Date(string='Date Completed')
rework_cost = fields.Float(string='Estimated Rework Cost (INR)', default=15.0)
defect_description = fields.Text(string='Defect Description & Alteration Instructions', required=True)
state = fields.Selection([
('assigned', 'Assigned for Rework'),
('in_rework', 'In Rework / Alteration'),
('ready_recheck', 'Ready for QA Re-check'),
('passed', 'Passed QA Re-check (Restored)'),
('scrapped', 'Irreparable / Scrapped'),
], string='Status', default='assigned', tracking=True, required=True)
@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.rework.order') or _('RWK/%s') % fields.Date.today().strftime('%Y%m%d')
return super(GarmentReworkOrder, self).create(vals_list)
def action_start_rework(self):
self.write({'state': 'in_rework'})
def action_ready_recheck(self):
self.write({'state': 'ready_recheck'})
def action_pass_recheck(self):
self.write({
'state': 'passed',
'passed_rework_qty': self.rework_qty,
'date_completed': fields.Date.today(),
})
def action_scrap_rework(self):
self.write({
'state': 'scrapped',
'scrapped_rework_qty': self.rework_qty,
'date_completed': fields.Date.today(),
})

View File

@ -0,0 +1,222 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import UserError
class SaleOrder(models.Model):
_inherit = 'sale.order'
is_garment_order = fields.Boolean(string='Garment Manufacturing Order', default=True)
garment_style_id = fields.Many2one('garment.style', string='Garment Style', tracking=True)
buyer_id = fields.Many2one('res.partner', string='Buyer / Brand', tracking=True)
buyer_po_ref = fields.Char(string='Buyer PO Reference', tracking=True)
season_id = fields.Many2one('garment.season', string='Season')
customer_style_ref = fields.Char(string='Buyer Style Code')
# Shipment Details
ex_factory_date = fields.Date(string='Ex-Factory Date', tracking=True)
shipment_date = fields.Date(string='Target Shipment Date', tracking=True)
shipment_mode = fields.Selection([
('sea', 'Sea Freight (FCL/LCL)'),
('air', 'Air Freight'),
('courier', 'Express Courier'),
('road', 'Road Transport'),
], string='Shipment Mode', default='sea')
destination_port = fields.Char(string='Destination Port / City')
# Order Matrix
order_matrix_line_ids = fields.One2many('garment.order.matrix.line', 'order_id', string='Size-Color Order Matrix')
total_garment_qty = fields.Integer(string='Total Garment Pieces', compute='_compute_total_garment_qty', store=True)
matrix_html_summary = fields.Html(string='Order Matrix Summary', compute='_compute_matrix_html_summary')
# Production Link
production_plan_ids = fields.One2many('garment.production.plan', 'sale_order_id', string='Production Plans')
production_plan_count = fields.Integer(string='Production Plans Count', compute='_compute_production_plan_count')
@api.onchange('garment_style_id')
def _onchange_garment_style_id(self):
if self.garment_style_id:
self.season_id = self.garment_style_id.season_id
self.customer_style_ref = self.garment_style_id.customer_style_ref
if self.garment_style_id.buyer_id and not self.buyer_id:
self.buyer_id = self.garment_style_id.buyer_id
@api.depends('order_matrix_line_ids.quantity')
def _compute_total_garment_qty(self):
for order in self:
order.total_garment_qty = sum(order.order_matrix_line_ids.mapped('quantity'))
@api.depends('order_matrix_line_ids.quantity', 'order_matrix_line_ids.color_id', 'order_matrix_line_ids.size_id')
def _compute_matrix_html_summary(self):
for order in self:
lines = order.order_matrix_line_ids
if not lines:
order.matrix_html_summary = "<p class='text-muted'>No matrix lines configured yet.</p>"
continue
# Distinct sorted colors and sizes
colors = sorted(lines.mapped('color_id'), key=lambda c: c.name)
sizes = sorted(lines.mapped('size_id'), key=lambda s: (s.sequence, s.code))
# Map (color_id, size_id) -> quantity
grid = {}
for l in lines:
grid[(l.color_id.id, l.size_id.id)] = grid.get((l.color_id.id, l.size_id.id), 0) + l.quantity
# Build HTML table
html = ["""
<div class="o_garment_matrix_container w-100 mb-3" style="width: 100% !important; max-width: 100%; overflow-x: auto; -webkit-overflow-scrolling: touch;">
<table class="table table-sm table-bordered text-center align-middle" style="width: 100% !important; min-width: 650px; margin-bottom: 0; font-size: 13.5px; border: 1px solid #dee2e6; border-collapse: collapse;">
<thead>
<tr style="background-color: #1e293b; color: #ffffff;">
<th class="text-start" style="min-width: 170px; padding: 10px 14px; background-color: #1e293b; color: #ffffff; border: 1px solid #334155; font-weight: 600;">Color \\ Size</th>
"""]
for s in sizes:
html.append(f"<th style='min-width: 80px; padding: 10px 8px; background-color: #1e293b; color: #ffffff; border: 1px solid #334155; font-weight: 600;'>{s.code}</th>")
html.append("<th style='min-width: 110px; padding: 10px 12px; background-color: #0f172a; color: #38bdf8; border: 1px solid #334155; font-weight: 700;'>Total Pcs</th></tr></thead><tbody>")
total_col = {s.id: 0 for s in sizes}
grand_total = 0
for idx, c in enumerate(colors):
c_hex = c.color_hex or '#eee'
row_bg = '#ffffff' if idx % 2 == 0 else '#f8fafc'
html.append(f"<tr style='background-color: {row_bg}; border-bottom: 1px solid #e2e8f0;'>")
html.append(f"<td class='text-start fw-semibold' style='padding: 8px 14px; white-space: nowrap; color: #1e293b;'><span style='display:inline-block;width:14px;height:14px;background-color:{c_hex};border:1px solid #94a3b8;margin-right:8px;border-radius:3px;vertical-align:middle;box-shadow:0 1px 2px rgba(0,0,0,0.15);'></span>{c.name}</td>")
row_sum = 0
for s in sizes:
qty = grid.get((c.id, s.id), 0)
row_sum += qty
total_col[s.id] += qty
cell_val = f"<span class='fw-semibold text-dark'>{qty:,}</span>" if qty else "<span style='color: #94a3b8;'>-</span>"
html.append(f"<td style='padding: 8px 6px; border: 1px solid #e2e8f0;'>{cell_val}</td>")
grand_total += row_sum
html.append(f"<td class='fw-bold' style='padding: 8px 12px; background-color: #f1f5f9; color: #0f172a; border: 1px solid #cbd5e1;'>{row_sum:,}</td></tr>")
# Footer total row
html.append("<tr style='background-color: #e2e8f0; font-weight: bold; border-top: 2px solid #94a3b8;'>")
html.append("<td class='text-start' style='padding: 10px 14px; color: #0f172a; font-weight: 700;'>Total Pcs</td>")
for s in sizes:
html.append(f"<td style='padding: 10px 6px; color: #0f172a; font-weight: 700; border: 1px solid #cbd5e1;'>{total_col[s.id]:,}</td>")
html.append(f"<td style='padding: 10px 12px; background-color: #dbeafe; color: #1d4ed8; font-size: 14px; font-weight: 800; border: 1px solid #93c5fd;'>{grand_total:,}</td></tr>")
html.append("</tbody></table></div>")
order.matrix_html_summary = "".join(html)
def action_sync_matrix_to_order_lines(self):
"""Generates or updates standard sale.order.line items from the Garment Matrix"""
self.ensure_one()
if not self.garment_style_id:
raise UserError(_("Please select a Garment Style before synchronizing order lines."))
if not self.order_matrix_line_ids:
raise UserError(_("Order Matrix is empty. Please enter size-color quantities first."))
# First ensure variants exist
self.garment_style_id.action_generate_product_variants()
ProductProduct = self.env['product.product']
# Clear existing lines linked to this style or create lines
created = 0
for m_line in self.order_matrix_line_ids:
if m_line.quantity <= 0:
continue
sku = f"{self.garment_style_id.name}-{m_line.color_id.code}-{m_line.size_id.code}"
product = ProductProduct.search([('default_code', '=', sku)], limit=1)
if not product:
product = ProductProduct.create({
'name': f"{self.garment_style_id.style_name} ({m_line.color_id.name}/{m_line.size_id.code})",
'default_code': sku,
'type': 'consu',
})
# Find or create sale.order.line
existing_line = self.order_line.filtered(lambda l: l.product_id.id == product.id)
if existing_line:
existing_line.write({
'product_uom_qty': m_line.quantity,
'price_unit': m_line.unit_price or existing_line.price_unit or 10.0,
})
else:
uom = self.garment_style_id.uom_id.id if self.garment_style_id.uom_id else self.env.ref('uom.product_uom_unit').id
sol_vals = {
'order_id': self.id,
'product_id': product.id,
'name': f"[{sku}] {self.garment_style_id.style_name} - {m_line.color_id.name} / {m_line.size_id.name}",
'product_uom_qty': m_line.quantity,
'price_unit': m_line.unit_price or 10.0,
}
if 'product_uom_id' in self.env['sale.order.line']._fields:
sol_vals['product_uom_id'] = uom
elif 'product_uom' in self.env['sale.order.line']._fields:
sol_vals['product_uom'] = uom
self.env['sale.order.line'].create(sol_vals)
created += 1
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'title': _('Sale Lines Synchronized'),
'message': _('Successfully synchronized %d product variant lines.') % created,
'sticky': False,
'type': 'success',
}
}
def action_create_production_plan(self):
"""Creates a Garment Production Plan from this Sales Order"""
self.ensure_one()
if not self.garment_style_id:
raise UserError(_("Please assign a Garment Style."))
if self.total_garment_qty <= 0:
raise UserError(_("Total order quantity must be greater than 0."))
plan = self.env['garment.production.plan'].create({
'sale_order_id': self.id,
'style_id': self.garment_style_id.id,
'planned_qty': self.total_garment_qty,
'date_start': fields.Date.today(),
'date_delivery': self.shipment_date or self.ex_factory_date or fields.Date.today(),
'company_id': self.company_id.id,
})
return {
'name': _('Garment Production Plan'),
'type': 'ir.actions.act_window',
'res_model': 'garment.production.plan',
'res_id': plan.id,
'view_mode': 'form',
'target': 'current',
}
@api.depends('production_plan_ids')
def _compute_production_plan_count(self):
for order in self:
order.production_plan_count = len(order.production_plan_ids)
def action_view_production_plans(self):
self.ensure_one()
return {
'name': _('Production Plans for SO %s') % self.name,
'type': 'ir.actions.act_window',
'res_model': 'garment.production.plan',
'view_mode': 'list,form',
'domain': [('sale_order_id', '=', self.id)],
'context': {'default_sale_order_id': self.id, 'default_style_id': self.garment_style_id.id},
}
class GarmentOrderMatrixLine(models.Model):
_name = 'garment.order.matrix.line'
_description = 'Garment Order Matrix Line (Color x Size x Quantity)'
_order = 'color_id, size_id'
order_id = fields.Many2one('sale.order', string='Sales Order', required=True, ondelete='cascade')
color_id = fields.Many2one('garment.color', string='Color', required=True)
size_id = fields.Many2one('garment.size', string='Size', required=True)
quantity = fields.Integer(string='Quantity (Pcs)', required=True, default=0)
unit_price = fields.Float(string='Unit Price', default=0.0)
subtotal = fields.Float(string='Subtotal', compute='_compute_subtotal', store=True)
@api.depends('quantity', 'unit_price')
def _compute_subtotal(self):
for rec in self:
rec.subtotal = rec.quantity * rec.unit_price

View File

@ -0,0 +1,134 @@
# -*- 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'

View File

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<!-- Sales Order Matrix Report Action -->
<record id="action_report_sales_order_matrix" model="ir.actions.report">
<field name="name">Garment Order Confirmation (Matrix)</field>
<field name="model">sale.order</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">garment_erp.report_sales_order_matrix_template</field>
<field name="report_file">garment_erp.report_sales_order_matrix_template</field>
<field name="binding_model_id" ref="sale.model_sale_order"/>
<field name="binding_type">report</field>
</record>
<!-- Production Plan Report Action -->
<record id="action_report_production_plan" model="ir.actions.report">
<field name="name">Production Plan &amp; Floor Traveler</field>
<field name="model">garment.production.plan</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">garment_erp.report_production_plan_template</field>
<field name="report_file">garment_erp.report_production_plan_template</field>
<field name="binding_model_id" ref="model_garment_production_plan"/>
<field name="binding_type">report</field>
</record>
<!-- Bundle Tickets Barcode Report Action -->
<record id="action_report_bundle_barcode" model="ir.actions.report">
<field name="name">Bundle Barcode Tickets</field>
<field name="model">garment.bundle</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">garment_erp.report_bundle_barcode_template</field>
<field name="report_file">garment_erp.report_bundle_barcode_template</field>
<field name="binding_model_id" ref="model_garment_bundle"/>
<field name="binding_type">report</field>
</record>
<!-- 4-Point Inspection Certificate -->
<record id="action_report_fabric_inspection" model="ir.actions.report">
<field name="name">Fabric 4-Point Inspection Certificate</field>
<field name="model">garment.fabric.inspection</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">garment_erp.report_fabric_inspection_template</field>
<field name="report_file">garment_erp.report_fabric_inspection_template</field>
<field name="binding_model_id" ref="model_garment_fabric_inspection"/>
<field name="binding_type">report</field>
</record>
<!-- Cutting Plan Lay Sheet -->
<record id="action_report_cutting_plan" model="ir.actions.report">
<field name="name">Cutting Lay &amp; Marker Sheet</field>
<field name="model">garment.cutting.plan</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">garment_erp.report_cutting_plan_template</field>
<field name="report_file">garment_erp.report_cutting_plan_template</field>
<field name="binding_model_id" ref="model_garment_cutting_plan"/>
<field name="binding_type">report</field>
</record>
<!-- Job Work Delivery Challan -->
<record id="action_report_job_work_challan" model="ir.actions.report">
<field name="name">Job Work Delivery Challan (GST)</field>
<field name="model">garment.jobwork.order</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">garment_erp.report_job_work_challan_template</field>
<field name="report_file">garment_erp.report_job_work_challan_template</field>
<field name="binding_model_id" ref="model_garment_jobwork_order"/>
<field name="binding_type">report</field>
</record>
<!-- Quality Inspection Audit Report -->
<record id="action_report_quality_inspection" model="ir.actions.report">
<field name="name">Quality Inspection Audit Certificate</field>
<field name="model">garment.quality.inspection</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">garment_erp.report_quality_inspection_template</field>
<field name="report_file">garment_erp.report_quality_inspection_template</field>
<field name="binding_model_id" ref="model_garment_quality_inspection"/>
<field name="binding_type">report</field>
</record>
<!-- Master Packing List Report -->
<record id="action_report_packing_list" model="ir.actions.report">
<field name="name">Master Export Packing List</field>
<field name="model">garment.packing.plan</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">garment_erp.report_packing_list_template</field>
<field name="report_file">garment_erp.report_packing_list_template</field>
<field name="binding_model_id" ref="model_garment_packing_plan"/>
<field name="binding_type">report</field>
</record>
<!-- Garment Costing Sheet Report -->
<record id="action_report_garment_costing" model="ir.actions.report">
<field name="name">Garment Pre-Costing Analysis</field>
<field name="model">garment.costing</field>
<field name="report_type">qweb-pdf</field>
<field name="report_name">garment_erp.report_garment_costing_template</field>
<field name="report_file">garment_erp.report_garment_costing_template</field>
<field name="binding_model_id" ref="model_garment_costing"/>
<field name="binding_type">report</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_bundle_barcode_template">
<t t-call="web.html_container">
<div class="page" style="font-family: Arial, sans-serif;">
<div class="row g-2">
<t t-foreach="docs" t-as="bundle">
<div class="col-6 mb-3">
<div class="p-3 border border-dark rounded" style="background:#fff; min-height: 280px;">
<div class="d-flex justify-content-between align-items-center border-bottom pb-1 mb-2">
<h5 class="fw-bold m-0 text-primary">BUNDLE TICKET</h5>
<span class="fs-5 fw-bold text-danger"><span t-field="bundle.name"/></span>
</div>
<div class="text-center my-2">
<img t-att-src="'/report/barcode/?barcode_type=Code128&amp;value=%s&amp;width=450&amp;height=80' % bundle.barcode" style="width: 85%; height: 50px;" alt="Barcode"/>
<div class="small fw-bold tracking-wider"><span t-field="bundle.barcode"/></div>
</div>
<div class="row small mb-2">
<div class="col-6">
<div><strong>Style:</strong> <span t-field="bundle.style_id.name"/></div>
<div><strong>Color:</strong> <span t-field="bundle.color_id.name"/></div>
<div><strong>Size:</strong> <span class="badge bg-dark fs-6"><span t-field="bundle.size_id.code"/></span></div>
</div>
<div class="col-6 text-end">
<div><strong>Quantity:</strong> <span class="fs-5 fw-bold text-success"><span t-field="bundle.quantity"/> Pcs</span></div>
<div><strong>Plies:</strong> <span t-field="bundle.ply_start"/> to <span t-field="bundle.ply_end"/></div>
<div><strong>Cut Order:</strong> <span t-field="bundle.cutting_plan_id.name"/></div>
</div>
</div>
<!-- Operator Signoff Table -->
<table class="table table-sm table-bordered text-center small mb-0" style="font-size: 10px;">
<thead class="table-light">
<tr>
<th>Operation</th>
<th>Operator ID</th>
<th>Qty Out</th>
<th>Sign / Date</th>
</tr>
</thead>
<tbody>
<tr><td>1. Overlock 4T</td><td></td><td></td><td></td></tr>
<tr><td>2. Flatlock Hem</td><td></td><td></td><td></td></tr>
<tr><td>3. Neck Rib Attach</td><td></td><td></td><td></td></tr>
<tr><td>4. Checking / QC</td><td></td><td></td><td></td></tr>
</tbody>
</table>
</div>
</div>
</t>
</div>
</div>
</t>
</template>
</odoo>

View File

@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_cutting_plan_template">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="cut">
<t t-call="web.external_layout">
<div class="page" style="font-family: Arial, sans-serif;">
<div class="row border-bottom pb-2 mb-3">
<div class="col-8">
<h2 class="text-primary fw-bold">Cutting Lay &amp; Marker Sheet</h2>
<h4>Order: <span t-field="cut.name"/></h4>
</div>
<div class="col-4 text-end">
<h6>Production Plan: <span t-field="cut.production_plan_id.name"/></h6>
<span class="badge bg-secondary">Status: <span t-field="cut.state"/></span>
</div>
</div>
<div class="row mb-3">
<div class="col-6">
<div><strong>Style:</strong> <span t-field="cut.style_id.name"/> - <span t-field="cut.style_id.style_name"/></div>
<div><strong>Color:</strong> <span t-field="cut.color_id.name"/></div>
<div><strong>Marker Code:</strong> <span t-field="cut.marker_no"/></div>
</div>
<div class="col-6 text-end">
<div><strong>Marker Length:</strong> <span t-field="cut.marker_length"/> Meters</div>
<div><strong>Marker Width:</strong> <span t-field="cut.marker_width"/> Inches</div>
<div><strong>Plies Count:</strong> <strong class="fs-5"><span t-field="cut.plies_count"/> Layers</strong></div>
</div>
</div>
<h5 class="fw-bold mt-4">Size Cut Ratio &amp; Expected Output</h5>
<table class="table table-bordered text-center">
<thead class="table-dark">
<tr>
<th>Size</th>
<th>Ratio per Ply</th>
<th>Plies</th>
<th>Expected Cut Pcs</th>
</tr>
</thead>
<tbody>
<tr t-foreach="cut.ratio_line_ids" t-as="r">
<td><span t-field="r.size_id.name"/></td>
<td><span t-field="r.ratio"/></td>
<td><span t-field="cut.plies_count"/></td>
<td class="fw-bold"><span t-field="r.expected_cut_qty"/></td>
</tr>
<tr class="table-light fw-bold">
<td colspan="3" class="text-end">Total Cut Pieces:</td>
<td class="text-primary fs-5"><span t-field="cut.actual_cut_qty"/></td>
</tr>
</tbody>
</table>
<h5 class="fw-bold mt-4">Fabric Rolls Issued &amp; Wastage Audit</h5>
<table class="table table-bordered text-center">
<thead class="table-secondary">
<tr>
<th>Roll #</th>
<th>Shade Lot</th>
<th>Weight Issued (Kg)</th>
<th>Weight Consumed (Kg)</th>
<th>End-Bit Scrap (Kg)</th>
</tr>
</thead>
<tbody>
<tr t-foreach="cut.roll_line_ids" t-as="roll">
<td><span t-field="roll.roll_id.name"/></td>
<td><span t-field="roll.shade_lot"/></td>
<td><span t-field="roll.weight_issued"/></td>
<td><span t-field="roll.weight_used"/></td>
<td><span t-field="roll.end_bit_scrap"/></td>
</tr>
<tr class="table-light fw-bold">
<td colspan="2" class="text-end">Totals:</td>
<td><span t-field="cut.total_fabric_issued_kg"/></td>
<td><span t-field="cut.total_fabric_used_kg"/></td>
<td class="text-danger"><span t-field="cut.end_bit_scrap_kg"/> (<span t-field="cut.cutting_wastage_pct"/>%)</td>
</tr>
</tbody>
</table>
<div class="row mt-5 text-center">
<div class="col-6"><div class="border-top pt-2"><strong>Lay Planner / Marker Operator</strong></div></div>
<div class="col-6"><div class="border-top pt-2"><strong>Cutting In-Charge</strong></div></div>
</div>
</div>
</t>
</t>
</t>
</template>
</odoo>

View File

@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_fabric_inspection_template">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="insp">
<t t-call="web.external_layout">
<div class="page" style="font-family: Arial, sans-serif;">
<div class="row border-bottom pb-2 mb-3">
<div class="col-8">
<h2 class="text-primary fw-bold">Fabric 4-Point Inspection Certificate</h2>
<h4>Certificate No: <span t-field="insp.name"/></h4>
</div>
<div class="col-4 text-end">
<h6>Inspection Date: <span t-field="insp.date_inspected"/></h6>
<span t-if="insp.state == 'passed'" class="badge bg-success fs-5">QUALITY APPROVED</span>
<span t-if="insp.state == 'failed'" class="badge bg-danger fs-5">QUALITY REJECTED</span>
</div>
</div>
<div class="row mb-3">
<div class="col-6">
<div><strong>Roll Number:</strong> <span t-field="insp.roll_id.name"/></div>
<div><strong>Fabric Product:</strong> <span t-field="insp.fabric_product_id.name"/></div>
<div><strong>Shade / Dyeing Lot:</strong> <span t-field="insp.shade_lot"/></div>
<div><strong>Supplier / Mill:</strong> <span t-field="insp.supplier_id.name"/></div>
</div>
<div class="col-6 text-end">
<div><strong>Inspector:</strong> <span t-field="insp.inspector_id.name"/></div>
<div><strong>Inspected Length:</strong> <span t-field="insp.inspected_meters"/> Meters</div>
<div><strong>Total Roll Length:</strong> <span t-field="insp.roll_id.length_meters"/> Meters</div>
</div>
</div>
<h5 class="fw-bold mt-4">Technical Verification &amp; Shade Grading</h5>
<table class="table table-bordered text-center">
<thead class="table-light">
<tr>
<th>Parameter</th>
<th>Target Specification</th>
<th>Actual Measured</th>
<th>Variance / Status</th>
</tr>
</thead>
<tbody>
<tr>
<td>Fabric GSM</td>
<td><span t-field="insp.target_gsm"/> g/m²</td>
<td><span t-field="insp.actual_gsm"/> g/m²</td>
<td><span t-esc="insp.actual_gsm - insp.target_gsm"/> g/m²</td>
</tr>
<tr>
<td>Fabric Width (Inches)</td>
<td><span t-field="insp.target_width"/>"</td>
<td><span t-field="insp.actual_width"/>"</td>
<td><span t-esc="insp.actual_width - insp.target_width"/>"</td>
</tr>
<tr>
<td>Shade Evaluation</td>
<td>Master Swatch Grade A</td>
<td colspan="2" class="fw-bold"><span t-field="insp.shade_grade"/></td>
</tr>
</tbody>
</table>
<h5 class="fw-bold mt-4">4-Point Defect Score Breakdown</h5>
<table class="table table-bordered text-center">
<thead class="table-secondary">
<tr>
<th>Defect Category</th>
<th>Defect Length</th>
<th>Penalty Points</th>
<th>Observed Count</th>
<th>Total Points</th>
</tr>
</thead>
<tbody>
<tr>
<td>Minor Flaw</td>
<td>Up to 3 inches</td>
<td>1 Point</td>
<td><span t-field="insp.defects_1pt_count"/></td>
<td><span t-esc="insp.defects_1pt_count * 1"/></td>
</tr>
<tr>
<td>Medium Flaw</td>
<td>Over 3" up to 6"</td>
<td>2 Points</td>
<td><span t-field="insp.defects_2pt_count"/></td>
<td><span t-esc="insp.defects_2pt_count * 2"/></td>
</tr>
<tr>
<td>Major Flaw</td>
<td>Over 6" up to 9"</td>
<td>3 Points</td>
<td><span t-field="insp.defects_3pt_count"/></td>
<td><span t-esc="insp.defects_3pt_count * 3"/></td>
</tr>
<tr>
<td>Critical / Hole</td>
<td>Over 9" or Any Hole</td>
<td>4 Points</td>
<td><span t-field="insp.defects_4pt_count"/></td>
<td><span t-esc="insp.defects_4pt_count * 4"/></td>
</tr>
<tr class="table-light fw-bold">
<td colspan="4" class="text-end">Total Defect Points:</td>
<td><span t-field="insp.total_defect_points"/></td>
</tr>
</tbody>
</table>
<div class="row mt-3 p-3 bg-light rounded text-center">
<div class="col-4">
<h6>Formula Score</h6>
<h3 class="text-primary fw-bold"><span t-field="insp.score_per_100_sqm"/></h3>
<small class="text-muted">Points / 100 Sq. Meters</small>
</div>
<div class="col-4">
<h6>Allowed Threshold</h6>
<h3 class="text-dark fw-bold">&lt;= <span t-field="insp.max_allowed_score"/></h3>
<small class="text-muted">Industry AQL Limit</small>
</div>
<div class="col-4">
<h6>Final Disposition</h6>
<h3 t-if="insp.state == 'passed'" class="text-success fw-bold">ACCEPTED</h3>
<h3 t-if="insp.state == 'failed'" class="text-danger fw-bold">REJECTED</h3>
</div>
</div>
<div class="mt-4">
<strong>Inspector Observations:</strong>
<p class="text-muted"><span t-field="insp.remarks"/></p>
</div>
<div class="row mt-5 text-center">
<div class="col-6"><div class="border-top pt-2"><strong>Quality Inspector</strong></div></div>
<div class="col-6"><div class="border-top pt-2"><strong>Quality Assurance Manager</strong></div></div>
</div>
</div>
</t>
</t>
</t>
</template>
</odoo>

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_garment_costing_template">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="cost">
<t t-call="web.external_layout">
<div class="page" style="font-family: Arial, sans-serif;">
<div class="row border-bottom pb-2 mb-3">
<div class="col-8">
<h2 class="text-primary fw-bold">Garment Pre-Costing &amp; Profitability Sheet</h2>
<h4>Cost Sheet: <span t-field="cost.name"/></h4>
</div>
<div class="col-4 text-end">
<h6>Order Qty: <strong class="fs-5"><span t-field="cost.order_qty"/> Pcs</strong></h6>
<span class="badge bg-secondary"><span t-field="cost.costing_type"/></span>
</div>
</div>
<div class="row mb-3">
<div class="col-6">
<div><strong>Style:</strong> <span t-field="cost.style_id.name"/> - <span t-field="cost.style_id.style_name"/></div>
<div><strong>Buyer:</strong> <span t-field="cost.buyer_id.name"/></div>
<div><strong>Fabric:</strong> <span t-field="cost.style_id.fabric_type_id.name"/> (<span t-field="cost.style_id.gsm"/> GSM)</div>
</div>
<div class="col-6 text-end">
<div><strong>Target Selling Price:</strong> <strong class="fs-5 text-primary"><span t-field="cost.target_selling_price" t-options="{'widget': 'monetary', 'display_currency': cost.currency_id}"/> / Pc</strong></div>
<div><strong>Total Garment Cost:</strong> <strong class="fs-5 text-danger"><span t-field="cost.total_cost_per_pc" t-options="{'widget': 'monetary', 'display_currency': cost.currency_id}"/> / Pc</strong></div>
<div><strong>Gross Margin:</strong> <strong class="fs-5 text-success"><span t-field="cost.gross_margin_pct"/>% (<span t-field="cost.gross_margin_amount" t-options="{'widget': 'monetary', 'display_currency': cost.currency_id}"/>)</strong></div>
</div>
</div>
<h5 class="fw-bold mt-4">Cost Structure Breakdown (Per Piece)</h5>
<table class="table table-bordered">
<thead class="table-dark">
<tr>
<th>Cost Element</th>
<th>Description / Operation</th>
<th class="text-end">Cost / Piece</th>
<th class="text-end">Cost / Dozen</th>
<th class="text-end">% of Total</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>1. Raw Fabric</strong></td>
<td>Yarn &amp; Knitting Cost</td>
<td class="text-end"><span t-field="cost.fabric_cost_per_pc" t-options="{'widget': 'monetary', 'display_currency': cost.currency_id}"/></td>
<td class="text-end"><span t-esc="'%.2f' % (cost.fabric_cost_per_pc * 12)"/></td>
<td class="text-end"><span t-esc="'%.1f' % ((cost.fabric_cost_per_pc / (cost.total_cost_per_pc or 1)) * 100)"/>%</td>
</tr>
<tr>
<td><strong>2. Fabric Processing</strong></td>
<td>Dyeing, Bleaching &amp; Bio-wash</td>
<td class="text-end"><span t-field="cost.dyeing_finishing_cost_per_pc" t-options="{'widget': 'monetary', 'display_currency': cost.currency_id}"/></td>
<td class="text-end"><span t-esc="'%.2f' % (cost.dyeing_finishing_cost_per_pc * 12)"/></td>
<td class="text-end"><span t-esc="'%.1f' % ((cost.dyeing_finishing_cost_per_pc / (cost.total_cost_per_pc or 1)) * 100)"/>%</td>
</tr>
<tr>
<td><strong>3. Trims &amp; Accessories</strong></td>
<td>Labels, Tags, Thread, Polybag</td>
<td class="text-end"><span t-field="cost.trims_accessories_cost_per_pc" t-options="{'widget': 'monetary', 'display_currency': cost.currency_id}"/></td>
<td class="text-end"><span t-esc="'%.2f' % (cost.trims_accessories_cost_per_pc * 12)"/></td>
<td class="text-end"><span t-esc="'%.1f' % ((cost.trims_accessories_cost_per_pc / (cost.total_cost_per_pc or 1)) * 100)"/>%</td>
</tr>
<tr>
<td><strong>4. Value Addition</strong></td>
<td>Chest Printing &amp; Embroidery</td>
<td class="text-end"><span t-field="cost.printing_embroidery_cost_per_pc" t-options="{'widget': 'monetary', 'display_currency': cost.currency_id}"/></td>
<td class="text-end"><span t-esc="'%.2f' % (cost.printing_embroidery_cost_per_pc * 12)"/></td>
<td class="text-end"><span t-esc="'%.1f' % ((cost.printing_embroidery_cost_per_pc / (cost.total_cost_per_pc or 1)) * 100)"/>%</td>
</tr>
<tr>
<td><strong>5. Cutting &amp; CM Labor</strong></td>
<td>Cutting, Sewing Assembly (<span t-field="cost.style_id.sam"/> SAM)</td>
<td class="text-end"><span t-esc="'%.2f' % (cost.cutting_cost_per_pc + cost.sewing_cm_cost_per_pc)"/></td>
<td class="text-end"><span t-esc="'%.2f' % ((cost.cutting_cost_per_pc + cost.sewing_cm_cost_per_pc) * 12)"/></td>
<td class="text-end"><span t-esc="'%.1f' % (((cost.cutting_cost_per_pc + cost.sewing_cm_cost_per_pc) / (cost.total_cost_per_pc or 1)) * 100)"/>%</td>
</tr>
<tr>
<td><strong>6. Packaging &amp; Freight</strong></td>
<td>Master Cartons, Local Handling</td>
<td class="text-end"><span t-esc="'%.2f' % (cost.packing_carton_cost_per_pc + cost.commercial_freight_cost_per_pc)"/></td>
<td class="text-end"><span t-esc="'%.2f' % ((cost.packing_carton_cost_per_pc + cost.commercial_freight_cost_per_pc) * 12)"/></td>
<td class="text-end"><span t-esc="'%.1f' % (((cost.packing_carton_cost_per_pc + cost.commercial_freight_cost_per_pc) / (cost.total_cost_per_pc or 1)) * 100)"/>%</td>
</tr>
<tr>
<td><strong>7. Factory Overheads</strong></td>
<td>Administration &amp; Factory Running (<span t-field="cost.factory_overhead_pct"/>%)</td>
<td class="text-end"><span t-field="cost.factory_overhead_cost" t-options="{'widget': 'monetary', 'display_currency': cost.currency_id}"/></td>
<td class="text-end"><span t-esc="'%.2f' % (cost.factory_overhead_cost * 12)"/></td>
<td class="text-end"><span t-esc="'%.1f' % ((cost.factory_overhead_cost / (cost.total_cost_per_pc or 1)) * 100)"/>%</td>
</tr>
<tr class="table-light fw-bold fs-6">
<td colspan="2" class="text-end">Total Manufacturing Cost:</td>
<td class="text-end text-danger"><span t-field="cost.total_cost_per_pc" t-options="{'widget': 'monetary', 'display_currency': cost.currency_id}"/></td>
<td class="text-end text-danger"><span t-field="cost.total_cost_per_dozen" t-options="{'widget': 'monetary', 'display_currency': cost.currency_id}"/></td>
<td class="text-end">100.0%</td>
</tr>
</tbody>
</table>
<div class="row my-4 p-3 bg-light border rounded text-center">
<div class="col-4">
<h6>Total Order Turnover</h6>
<h3 class="text-primary fw-bold"><span t-esc="'%.2f' % (cost.target_selling_price * cost.order_qty)"/></h3>
</div>
<div class="col-4">
<h6>Total Production Cost</h6>
<h3 class="text-dark fw-bold"><span t-field="cost.total_order_cost" t-options="{'widget': 'monetary', 'display_currency': cost.currency_id}"/></h3>
</div>
<div class="col-4">
<h6>Total Expected Profit</h6>
<h3 class="text-success fw-bold"><span t-field="cost.total_expected_profit" t-options="{'widget': 'monetary', 'display_currency': cost.currency_id}"/></h3>
</div>
</div>
<div class="row mt-5 text-center">
<div class="col-6"><div class="border-top pt-2"><strong>Merchandiser / Cost Accountant</strong></div></div>
<div class="col-6"><div class="border-top pt-2"><strong>Managing Director / CEO</strong></div></div>
</div>
</div>
</t>
</t>
</t>
</template>
</odoo>

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_job_work_challan_template">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="jw">
<t t-call="web.external_layout">
<div class="page" style="font-family: Arial, sans-serif;">
<div class="row border-bottom pb-2 mb-3">
<div class="col-8">
<h2 class="text-primary fw-bold">JOB WORK DELIVERY CHALLAN</h2>
<small class="text-muted">(Issued under Section 143 of CGST Act, 2017 / Rule 55)</small>
<h4>DC No: <span t-field="jw.name"/></h4>
</div>
<div class="col-4 text-end">
<h6>DC Date: <span t-field="jw.date_sent"/></h6>
<h6>Expected Return: <span t-field="jw.date_expected"/></h6>
<h6>Vehicle No: <span t-field="jw.vehicle_no"/></h6>
</div>
</div>
<div class="row mb-4">
<div class="col-6">
<div class="p-2 border rounded bg-light">
<h6 class="fw-bold border-bottom pb-1">Subcontractor (Job Worker)</h6>
<div class="fw-bold"><span t-field="jw.vendor_id.name"/></div>
<div><span t-field="jw.vendor_id.street"/></div>
<div><span t-field="jw.vendor_id.city"/>, <span t-field="jw.vendor_id.state_id.name"/></div>
<div>GSTIN: <span t-field="jw.vendor_id.vat"/></div>
</div>
</div>
<div class="col-6">
<div class="p-2 border rounded bg-light">
<h6 class="fw-bold border-bottom pb-1">Process Nature</h6>
<div>Process: <strong class="text-primary"><span t-field="jw.process_type"/></strong></div>
<div>Production Plan: <span t-field="jw.production_plan_id.name"/></div>
<div>Style: <span t-field="jw.style_id.name"/></div>
<div>Allowed Process Loss: <span t-field="jw.allowed_process_loss_pct"/>%</div>
</div>
</div>
</div>
<h5 class="fw-bold mt-4">Materials Sent Outward</h5>
<table class="table table-bordered text-center">
<thead class="table-dark">
<tr>
<th>Description of Goods</th>
<th>Roll / Bundle No</th>
<th>Color</th>
<th>Quantity</th>
<th>UoM</th>
</tr>
</thead>
<tbody>
<tr t-foreach="jw.sent_line_ids" t-as="line">
<td class="text-start"><span t-field="line.description"/></td>
<td><span t-field="line.fabric_roll_id.name"/><span t-field="line.bundle_id.name"/></td>
<td><span t-field="line.color_id.name"/></td>
<td class="fw-bold"><span t-field="line.quantity"/></td>
<td><span t-field="line.uom"/></td>
</tr>
<tr class="table-light fw-bold">
<td colspan="3" class="text-end">Total Sent:</td>
<td class="text-primary fs-5"><span t-field="jw.total_sent_qty"/></td>
<td></td>
</tr>
</tbody>
</table>
<div class="mt-4">
<strong>Special Processing Directives:</strong>
<p class="text-muted"><span t-field="jw.notes"/></p>
</div>
<div class="row mt-5 pt-4 text-center">
<div class="col-6"><div class="border-top pt-2"><strong>Sender Authorized Signature</strong></div></div>
<div class="col-6"><div class="border-top pt-2"><strong>Job Worker Acknowledgement &amp; Seal</strong></div></div>
</div>
</div>
</t>
</t>
</t>
</template>
</odoo>

View File

@ -0,0 +1,81 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_packing_list_template">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="pack">
<t t-call="web.external_layout">
<div class="page" style="font-family: Arial, sans-serif;">
<div class="row border-bottom pb-2 mb-3">
<div class="col-8">
<h2 class="text-primary fw-bold">MASTER EXPORT PACKING LIST</h2>
<h4>Packing Plan Ref: <span t-field="pack.name"/></h4>
</div>
<div class="col-4 text-end">
<h6>Order Ref: <span t-field="pack.sale_order_id.name"/></h6>
<h6>Buyer PO: <span t-field="pack.sale_order_id.buyer_po_ref"/></h6>
</div>
</div>
<div class="row mb-3">
<div class="col-6">
<div><strong>Buyer:</strong> <span t-field="pack.sale_order_id.partner_id.name"/></div>
<div><strong>Style:</strong> <span t-field="pack.style_id.name"/> - <span t-field="pack.style_id.style_name"/></div>
<div><strong>Packing Method:</strong> <span t-field="pack.packing_type"/></div>
</div>
<div class="col-6 text-end">
<div><strong>Total Cartons:</strong> <strong class="fs-5 text-primary"><span t-field="pack.total_cartons"/> Boxes</strong></div>
<div><strong>Total Packed Quantity:</strong> <strong class="fs-5 text-success"><span t-field="pack.total_packed_qty"/> Pcs</strong></div>
<div><strong>Total Shipment Volume:</strong> <strong class="fs-5 text-dark"><span t-field="pack.total_cbm"/> CBM</strong></div>
<div><strong>Total Gross Weight:</strong> <span t-field="pack.total_gross_weight"/> Kg</div>
</div>
</div>
<h5 class="fw-bold mt-4">Carton-Wise Packing Breakdown</h5>
<table class="table table-bordered table-sm text-center" style="font-size:12px;">
<thead class="table-dark">
<tr>
<th>Ctn #</th>
<th>Carton ID</th>
<th>Barcode / QR</th>
<th>Garment Breakdown</th>
<th>Total Pcs</th>
<th>Dimensions (cm)</th>
<th>CBM</th>
<th>Gross Kg</th>
</tr>
</thead>
<tbody>
<tr t-foreach="pack.carton_ids" t-as="ctn">
<td class="fw-bold"><span t-field="ctn.carton_no"/></td>
<td><span t-field="ctn.name"/></td>
<td><span t-field="ctn.barcode"/></td>
<td class="text-start">
<t t-foreach="ctn.line_ids" t-as="cl">
<span><span t-field="cl.color_id.name"/> / <span t-field="cl.size_id.code"/>: <strong><span t-field="cl.quantity"/></strong>; </span>
</t>
</td>
<td class="fw-bold"><span t-field="ctn.total_pieces"/></td>
<td><span t-field="ctn.length_cm"/>x<span t-field="ctn.width_cm"/>x<span t-field="ctn.height_cm"/></td>
<td><span t-field="ctn.cbm"/></td>
<td><span t-field="ctn.gross_weight_kg"/></td>
</tr>
<tr class="table-secondary fw-bold">
<td colspan="4" class="text-end">Grand Totals:</td>
<td class="text-success"><span t-field="pack.total_packed_qty"/> Pcs</td>
<td></td>
<td><span t-field="pack.total_cbm"/></td>
<td><span t-field="pack.total_gross_weight"/> Kg</td>
</tr>
</tbody>
</table>
<div class="row mt-5 text-center">
<div class="col-6"><div class="border-top pt-2"><strong>Packing Supervisor</strong></div></div>
<div class="col-6"><div class="border-top pt-2"><strong>Shipping &amp; Logistics Manager</strong></div></div>
</div>
</div>
</t>
</t>
</t>
</template>
</odoo>

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_production_plan_template">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="plan">
<t t-call="web.external_layout">
<div class="page" style="font-family: Arial, sans-serif;">
<div class="row border-bottom pb-2 mb-3">
<div class="col-8">
<h2 class="text-primary fw-bold">Garment Production Master Traveler</h2>
<h4>Plan Ref: <span t-field="plan.name"/></h4>
</div>
<div class="col-4 text-end">
<h6>Start Date: <span t-field="plan.date_start"/></h6>
<h6>Delivery Date: <span t-field="plan.date_delivery"/></h6>
<span class="badge bg-secondary">Status: <span t-field="plan.state"/></span>
</div>
</div>
<div class="row mb-3">
<div class="col-6">
<h6><strong>Style:</strong> <span t-field="plan.style_id.name"/> - <span t-field="plan.style_id.style_name"/></h6>
<h6><strong>Sales Order:</strong> <span t-field="plan.sale_order_id.name"/></h6>
<h6><strong>Fabric Structure:</strong> <span t-field="plan.style_id.fabric_type_id.name"/> (<span t-field="plan.style_id.gsm"/> GSM)</h6>
</div>
<div class="col-6 text-end">
<h3>Planned Quantity: <strong class="text-primary"><span t-field="plan.planned_qty"/> Pcs</strong></h3>
<h6>Sewing SAM: <span t-field="plan.style_id.sam"/> Minutes</h6>
</div>
</div>
<h5 class="fw-bold mt-4">Size × Color Production Target Matrix</h5>
<table class="table table-sm table-bordered text-center">
<thead class="table-dark">
<tr>
<th>Color</th>
<th>Size</th>
<th>Planned Pcs</th>
<th>Cut Pcs</th>
<th>Sewn Pcs</th>
<th>Packed Pcs</th>
<th>Balance</th>
</tr>
</thead>
<tbody>
<tr t-foreach="plan.line_ids" t-as="line">
<td class="text-start fw-bold"><span t-field="line.color_id.name"/></td>
<td><span t-field="line.size_id.name"/></td>
<td class="fw-bold"><span t-field="line.planned_qty"/></td>
<td><span t-field="line.cut_qty"/></td>
<td><span t-field="line.sewn_qty"/></td>
<td><span t-field="line.packed_qty"/></td>
<td class="text-danger"><span t-field="line.variance"/></td>
</tr>
</tbody>
</table>
<h5 class="fw-bold mt-4">Required Bill of Materials &amp; Consumption</h5>
<table class="table table-sm table-bordered" t-if="plan.bom_id">
<thead class="table-secondary">
<tr>
<th>Component</th>
<th>Specification</th>
<th>Gross Cons / Pc</th>
<th>Total Order Requirement</th>
</tr>
</thead>
<tbody>
<tr t-foreach="plan.bom_id.fabric_line_ids" t-as="fab">
<td><span t-field="fab.component_type"/></td>
<td><span t-field="fab.fabric_type_id.name"/> (<span t-field="fab.gsm"/> GSM)</td>
<td><span t-field="fab.gross_consumption_kg"/> Kg</td>
<td class="fw-bold"><span t-esc="'%.1f' % (fab.gross_consumption_kg * plan.planned_qty)"/> Kg</td>
</tr>
</tbody>
</table>
<div class="row mt-5 text-center">
<div class="col-4"><div class="border-top pt-2"><strong>Cutting Master</strong></div></div>
<div class="col-4"><div class="border-top pt-2"><strong>Sewing Supervisor</strong></div></div>
<div class="col-4"><div class="border-top pt-2"><strong>Factory Manager</strong></div></div>
</div>
</div>
</t>
</t>
</t>
</template>
</odoo>

View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_quality_inspection_template">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="qc">
<t t-call="web.external_layout">
<div class="page" style="font-family: Arial, sans-serif;">
<div class="row border-bottom pb-2 mb-3">
<div class="col-8">
<h2 class="text-primary fw-bold">Garment Quality Inspection Certificate</h2>
<h4>Audit Report: <span t-field="qc.name"/></h4>
</div>
<div class="col-4 text-end">
<h6>Date: <span t-field="qc.date_inspected"/></h6>
<h5 t-if="qc.verdict == 'passed'" class="badge bg-success fs-5">AUDIT PASSED</h5>
<h5 t-if="qc.verdict == 'rework_needed'" class="badge bg-warning fs-5">REWORK REQUIRED</h5>
<h5 t-if="qc.verdict == 'rejected'" class="badge bg-danger fs-5">AUDIT FAILED</h5>
</div>
</div>
<div class="row mb-3">
<div class="col-6">
<div><strong>Inspection Stage:</strong> <span t-field="qc.inspection_stage"/></div>
<div><strong>Style:</strong> <span t-field="qc.style_id.name"/> - <span t-field="qc.style_id.style_name"/></div>
<div><strong>Color / Size:</strong> <span t-field="qc.color_id.name"/> / <span t-field="qc.size_id.name"/></div>
<div><strong>Sewing Line:</strong> <span t-field="qc.sewing_line_id.name"/></div>
</div>
<div class="col-6 text-end">
<div><strong>Auditor:</strong> <span t-field="qc.inspector_id.name"/></div>
<div><strong>Lot Quantity:</strong> <span t-field="qc.total_lot_qty"/> Pcs</div>
<div><strong>Sample Size Audited:</strong> <span t-field="qc.sample_size"/> Pcs</div>
</div>
</div>
<div class="row my-3 p-3 bg-light rounded text-center">
<div class="col-3">
<h6>Sample Checked</h6>
<div class="fs-4 fw-bold text-dark"><span t-field="qc.sample_size"/> Pcs</div>
</div>
<div class="col-3">
<h6>Passed</h6>
<div class="fs-4 fw-bold text-success"><span t-field="qc.passed_qty"/> Pcs</div>
</div>
<div class="col-3">
<h6>Defective / Failed</h6>
<div class="fs-4 fw-bold text-danger"><span t-field="qc.failed_qty"/> Pcs</div>
</div>
<div class="col-3">
<h6>Rework Routing</h6>
<div class="fs-4 fw-bold text-warning"><span t-field="qc.rework_qty"/> Pcs</div>
</div>
</div>
<h5 class="fw-bold mt-4">Observed Defects Breakdown</h5>
<table class="table table-bordered text-center">
<thead class="table-secondary">
<tr>
<th>Defect Type</th>
<th>Category</th>
<th>Observed Count</th>
<th>Corrective Action</th>
<th>Notes</th>
</tr>
</thead>
<tbody>
<tr t-foreach="qc.defect_line_ids" t-as="d">
<td class="text-start"><span t-field="d.defect_type_id.name"/></td>
<td><span t-field="d.category"/></td>
<td class="fw-bold text-danger"><span t-field="d.defect_count"/></td>
<td><span t-field="d.action_type"/></td>
<td><span t-field="d.notes"/></td>
</tr>
</tbody>
</table>
<div class="mt-4">
<strong>Quality Auditor Remarks:</strong>
<p class="text-muted"><span t-field="qc.remarks"/></p>
</div>
<div class="row mt-5 text-center">
<div class="col-6"><div class="border-top pt-2"><strong>Quality Control Inspector</strong></div></div>
<div class="col-6"><div class="border-top pt-2"><strong>Quality Assurance Manager</strong></div></div>
</div>
</div>
</t>
</t>
</t>
</template>
</odoo>

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<template id="report_sales_order_matrix_template">
<t t-call="web.html_container">
<t t-foreach="docs" t-as="doc">
<t t-call="web.external_layout">
<div class="page" style="font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;">
<div class="row border-bottom pb-2 mb-3">
<div class="col-8">
<h2 class="text-primary fw-bold">Garment Export Order Confirmation</h2>
<h5 class="text-muted">Order Ref: <span t-field="doc.name"/></h5>
</div>
<div class="col-4 text-end">
<h6>Date: <span t-field="doc.date_order" t-options="{'widget': 'date'}"/></h6>
<h6>Ex-Factory: <span t-field="doc.ex_factory_date"/></h6>
</div>
</div>
<div class="row mb-4">
<div class="col-6">
<div class="p-2 border rounded bg-light">
<h6 class="fw-bold border-bottom pb-1">Buyer / Consignee</h6>
<div class="fw-bold"><span t-field="doc.buyer_id.name"/></div>
<div><span t-field="doc.buyer_id.street"/></div>
<div><span t-field="doc.buyer_id.city"/>, <span t-field="doc.buyer_id.country_id.name"/></div>
<div>Buyer PO Ref: <strong class="text-dark"><span t-field="doc.buyer_po_ref"/></strong></div>
</div>
</div>
<div class="col-6">
<div class="p-2 border rounded bg-light">
<h6 class="fw-bold border-bottom pb-1">Garment Style Specifications</h6>
<div>Style No: <strong class="text-primary"><span t-field="doc.garment_style_id.name"/></strong></div>
<div>Description: <span t-field="doc.garment_style_id.style_name"/></div>
<div>Fabric: <span t-field="doc.garment_style_id.fabric_type_id.name"/> (<span t-field="doc.garment_style_id.gsm"/> GSM)</div>
<div>Season: <span t-field="doc.season_id.name"/></div>
<div>Shipment Mode: <span t-field="doc.shipment_mode"/> · Destination: <span t-field="doc.destination_port"/></div>
</div>
</div>
</div>
<!-- Matrix Table -->
<h5 class="fw-bold text-dark mt-4 mb-2">Order Matrix (Size × Color Breakdown)</h5>
<div t-out="doc.matrix_html_summary"/>
<div class="row mt-4">
<div class="col-6">
<p class="small text-muted">
<strong>Inspection Standard:</strong> AQL 2.5 Major / 4.0 Minor<br/>
<strong>Packaging:</strong> Export standard master cartons with individual polybag<br/>
<strong>Terms of Delivery:</strong> FOB Tuticorin / Ex-Factory Tiruppur
</p>
</div>
<div class="col-6 text-end">
<h4>Total Garments: <strong class="text-primary"><span t-field="doc.total_garment_qty"/> Pcs</strong></h4>
<h4>Total Order Value: <strong class="text-success"><span t-field="doc.amount_total" t-options="{'widget': 'monetary', 'display_currency': doc.currency_id}"/></strong></h4>
</div>
</div>
<div class="row mt-5 pt-4 text-center">
<div class="col-6">
<div class="border-top pt-2" style="width: 70%; margin: 0 auto;">
<strong>Authorized Signature (Buyer)</strong>
</div>
</div>
<div class="col-6">
<div class="border-top pt-2" style="width: 70%; margin: 0 auto;">
<strong>Authorized Signatory (Manufacturer)</strong>
</div>
</div>
</div>
</div>
</t>
</t>
</t>
</template>
</odoo>

View File

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="0">
<!-- Module Category -->
<record id="module_category_garment_erp" model="ir.module.category">
<field name="name">Garment Manufacturing</field>
<field name="description">Comprehensive Garment Manufacturing ERP for Tiruppur Apparel Industry</field>
<field name="sequence">20</field>
</record>
<!-- Odoo 19 Privilege Grouping -->
<record id="privilege_garment_erp" model="res.groups.privilege">
<field name="name">Garment Manufacturing</field>
<field name="sequence">10</field>
<field name="category_id" ref="module_category_garment_erp"/>
</record>
<!-- Garment User (Base Read Only) -->
<record id="group_garment_user" model="res.groups">
<field name="name">Garment User (Viewer)</field>
<field name="sequence">10</field>
<field name="privilege_id" ref="privilege_garment_erp"/>
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
</record>
<!-- Garment Merchandiser -->
<record id="group_garment_merchandiser" model="res.groups">
<field name="name">Garment Merchandiser</field>
<field name="sequence">20</field>
<field name="privilege_id" ref="privilege_garment_erp"/>
<field name="implied_ids" eval="[(4, ref('group_garment_user'))]"/>
</record>
<!-- Garment Production Manager -->
<record id="group_garment_production" model="res.groups">
<field name="name">Garment Production Manager</field>
<field name="sequence">30</field>
<field name="privilege_id" ref="privilege_garment_erp"/>
<field name="implied_ids" eval="[(4, ref('group_garment_user'))]"/>
</record>
<!-- Garment Stores & Fabric Manager -->
<record id="group_garment_inventory" model="res.groups">
<field name="name">Fabric &amp; Stores Manager</field>
<field name="sequence">40</field>
<field name="privilege_id" ref="privilege_garment_erp"/>
<field name="implied_ids" eval="[(4, ref('group_garment_user'))]"/>
</record>
<!-- Garment Quality Manager -->
<record id="group_garment_quality" model="res.groups">
<field name="name">Garment Quality Manager</field>
<field name="sequence">50</field>
<field name="privilege_id" ref="privilege_garment_erp"/>
<field name="implied_ids" eval="[(4, ref('group_garment_user'))]"/>
</record>
<!-- Garment Job Work Manager -->
<record id="group_garment_jobwork" model="res.groups">
<field name="name">Subcontracting &amp; Job Work Manager</field>
<field name="sequence">60</field>
<field name="privilege_id" ref="privilege_garment_erp"/>
<field name="implied_ids" eval="[(4, ref('group_garment_user'))]"/>
</record>
<!-- Garment Packing & Dispatch Manager -->
<record id="group_garment_packing" model="res.groups">
<field name="name">Packing &amp; Dispatch Manager</field>
<field name="sequence">70</field>
<field name="privilege_id" ref="privilege_garment_erp"/>
<field name="implied_ids" eval="[(4, ref('group_garment_user'))]"/>
</record>
<!-- Garment Administrator -->
<record id="group_garment_admin" model="res.groups">
<field name="name">Garment ERP Administrator</field>
<field name="sequence">80</field>
<field name="privilege_id" ref="privilege_garment_erp"/>
<field name="implied_ids" eval="[(4, ref('group_garment_merchandiser')), (4, ref('group_garment_production')), (4, ref('group_garment_inventory')), (4, ref('group_garment_quality')), (4, ref('group_garment_jobwork')), (4, ref('group_garment_packing'))]"/>
<field name="user_ids" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
</record>
</data>
<!-- Multi-Company Rules -->
<data noupdate="1">
<record id="garment_production_plan_company_rule" model="ir.rule">
<field name="name">Garment Production Plan Multi-Company</field>
<field name="model_id" ref="model_garment_production_plan"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
</record>
<record id="garment_jobwork_company_rule" model="ir.rule">
<field name="name">Garment Jobwork Multi-Company</field>
<field name="model_id" ref="model_garment_jobwork_order"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
</record>
<record id="garment_packing_company_rule" model="ir.rule">
<field name="name">Garment Packing Multi-Company</field>
<field name="model_id" ref="model_garment_packing_plan"/>
<field name="domain_force">['|', ('company_id', '=', False), ('company_id', 'in', company_ids)]</field>
</record>
</data>
</odoo>

View File

@ -0,0 +1,104 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_garment_brand_user,garment.brand.user,model_garment_brand,group_garment_user,1,0,0,0
access_garment_brand_admin,garment.brand.admin,model_garment_brand,group_garment_admin,1,1,1,1
access_garment_season_user,garment.season.user,model_garment_season,group_garment_user,1,0,0,0
access_garment_season_admin,garment.season.admin,model_garment_season,group_garment_admin,1,1,1,1
access_garment_gender_user,garment.gender.user,model_garment_gender,group_garment_user,1,0,0,0
access_garment_gender_admin,garment.gender.admin,model_garment_gender,group_garment_admin,1,1,1,1
access_garment_type_user,garment.type.user,model_garment_type,group_garment_user,1,0,0,0
access_garment_type_admin,garment.type.admin,model_garment_type,group_garment_admin,1,1,1,1
access_garment_fabric_type_user,garment.fabric.type.user,model_garment_fabric_type,group_garment_user,1,0,0,0
access_garment_fabric_type_admin,garment.fabric.type.admin,model_garment_fabric_type,group_garment_admin,1,1,1,1
access_garment_composition_user,garment.composition.user,model_garment_composition,group_garment_user,1,0,0,0
access_garment_composition_admin,garment.composition.admin,model_garment_composition,group_garment_admin,1,1,1,1
access_garment_color_user,garment.color.user,model_garment_color,group_garment_user,1,0,0,0
access_garment_color_admin,garment.color.admin,model_garment_color,group_garment_admin,1,1,1,1
access_garment_size_user,garment.size.user,model_garment_size,group_garment_user,1,0,0,0
access_garment_size_admin,garment.size.admin,model_garment_size,group_garment_admin,1,1,1,1
access_garment_style_user,garment.style.user,model_garment_style,group_garment_user,1,0,0,0
access_garment_style_merch,garment.style.merch,model_garment_style,group_garment_merchandiser,1,1,1,1
access_garment_style_admin,garment.style.admin,model_garment_style,group_garment_admin,1,1,1,1
access_garment_style_measurement_user,garment.style.measurement.user,model_garment_style_measurement,group_garment_user,1,0,0,0
access_garment_style_measurement_admin,garment.style.measurement.admin,model_garment_style_measurement,group_garment_admin,1,1,1,1
access_garment_order_matrix_line_user,garment.order.matrix.line.user,model_garment_order_matrix_line,group_garment_user,1,0,0,0
access_garment_order_matrix_line_merch,garment.order.matrix.line.merch,model_garment_order_matrix_line,group_garment_merchandiser,1,1,1,1
access_garment_order_matrix_line_admin,garment.order.matrix.line.admin,model_garment_order_matrix_line,group_garment_admin,1,1,1,1
access_garment_bom_user,garment.bom.user,model_garment_bom,group_garment_user,1,0,0,0
access_garment_bom_prod,garment.bom.prod,model_garment_bom,group_garment_production,1,1,1,1
access_garment_bom_admin,garment.bom.admin,model_garment_bom,group_garment_admin,1,1,1,1
access_garment_bom_fabric_user,garment.bom.fabric.user,model_garment_bom_fabric,group_garment_user,1,0,0,0
access_garment_bom_fabric_admin,garment.bom.fabric.admin,model_garment_bom_fabric,group_garment_admin,1,1,1,1
access_garment_bom_trim_user,garment.bom.trim.user,model_garment_bom_trim,group_garment_user,1,0,0,0
access_garment_bom_trim_admin,garment.bom.trim.admin,model_garment_bom_trim,group_garment_admin,1,1,1,1
access_garment_bom_operation_user,garment.bom.operation.user,model_garment_bom_operation,group_garment_user,1,0,0,0
access_garment_bom_operation_admin,garment.bom.operation.admin,model_garment_bom_operation,group_garment_admin,1,1,1,1
access_garment_production_plan_user,garment.production.plan.user,model_garment_production_plan,group_garment_user,1,0,0,0
access_garment_production_plan_prod,garment.production.plan.prod,model_garment_production_plan,group_garment_production,1,1,1,1
access_garment_production_plan_admin,garment.production.plan.admin,model_garment_production_plan,group_garment_admin,1,1,1,1
access_garment_production_plan_line_user,garment.production.plan.line.user,model_garment_production_plan_line,group_garment_user,1,0,0,0
access_garment_production_plan_line_admin,garment.production.plan.line.admin,model_garment_production_plan_line,group_garment_admin,1,1,1,1
access_garment_fabric_roll_user,garment.fabric.roll.user,model_garment_fabric_roll,group_garment_user,1,0,0,0
access_garment_fabric_roll_inv,garment.fabric.roll.inv,model_garment_fabric_roll,group_garment_inventory,1,1,1,1
access_garment_fabric_roll_admin,garment.fabric.roll.admin,model_garment_fabric_roll,group_garment_admin,1,1,1,1
access_garment_fabric_inspection_user,garment.fabric.inspection.user,model_garment_fabric_inspection,group_garment_user,1,0,0,0
access_garment_fabric_inspection_qc,garment.fabric.inspection.qc,model_garment_fabric_inspection,group_garment_quality,1,1,1,1
access_garment_fabric_inspection_admin,garment.fabric.inspection.admin,model_garment_fabric_inspection,group_garment_admin,1,1,1,1
access_garment_mrp_requirement_user,garment.mrp.requirement.user,model_garment_mrp_requirement,group_garment_user,1,0,0,0
access_garment_mrp_requirement_prod,garment.mrp.requirement.prod,model_garment_mrp_requirement,group_garment_production,1,1,1,1
access_garment_mrp_requirement_admin,garment.mrp.requirement.admin,model_garment_mrp_requirement,group_garment_admin,1,1,1,1
access_garment_mrp_requirement_line_user,garment.mrp.requirement.line.user,model_garment_mrp_requirement_line,group_garment_user,1,0,0,0
access_garment_mrp_requirement_line_admin,garment.mrp.requirement.line.admin,model_garment_mrp_requirement_line,group_garment_admin,1,1,1,1
access_garment_cutting_plan_user,garment.cutting.plan.user,model_garment_cutting_plan,group_garment_user,1,0,0,0
access_garment_cutting_plan_prod,garment.cutting.plan.prod,model_garment_cutting_plan,group_garment_production,1,1,1,1
access_garment_cutting_plan_admin,garment.cutting.plan.admin,model_garment_cutting_plan,group_garment_admin,1,1,1,1
access_garment_cutting_ratio_line_user,garment.cutting.ratio.line.user,model_garment_cutting_ratio_line,group_garment_user,1,0,0,0
access_garment_cutting_ratio_line_admin,garment.cutting.ratio.line.admin,model_garment_cutting_ratio_line,group_garment_admin,1,1,1,1
access_garment_cutting_roll_line_user,garment.cutting.roll.line.user,model_garment_cutting_roll_line,group_garment_user,1,0,0,0
access_garment_cutting_roll_line_admin,garment.cutting.roll.line.admin,model_garment_cutting_roll_line,group_garment_admin,1,1,1,1
access_garment_bundle_user,garment.bundle.user,model_garment_bundle,group_garment_user,1,0,0,0
access_garment_bundle_prod,garment.bundle.prod,model_garment_bundle,group_garment_production,1,1,1,1
access_garment_bundle_admin,garment.bundle.admin,model_garment_bundle,group_garment_admin,1,1,1,1
access_garment_bundle_history_user,garment.bundle.history.user,model_garment_bundle_history,group_garment_user,1,0,0,0
access_garment_bundle_history_admin,garment.bundle.history.admin,model_garment_bundle_history,group_garment_admin,1,1,1,1
access_garment_sewing_line_user,garment.sewing.line.user,model_garment_sewing_line,group_garment_user,1,0,0,0
access_garment_sewing_line_prod,garment.sewing.line.prod,model_garment_sewing_line,group_garment_production,1,1,1,1
access_garment_sewing_line_admin,garment.sewing.line.admin,model_garment_sewing_line,group_garment_admin,1,1,1,1
access_garment_sewing_hourly_user,garment.sewing.hourly.user,model_garment_sewing_hourly,group_garment_user,1,0,0,0
access_garment_sewing_hourly_prod,garment.sewing.hourly.prod,model_garment_sewing_hourly,group_garment_production,1,1,1,1
access_garment_sewing_hourly_admin,garment.sewing.hourly.admin,model_garment_sewing_hourly,group_garment_admin,1,1,1,1
access_garment_jobwork_order_user,garment.jobwork.order.user,model_garment_jobwork_order,group_garment_user,1,0,0,0
access_garment_jobwork_order_jw,garment.jobwork.order.jw,model_garment_jobwork_order,group_garment_jobwork,1,1,1,1
access_garment_jobwork_order_admin,garment.jobwork.order.admin,model_garment_jobwork_order,group_garment_admin,1,1,1,1
access_garment_jobwork_sent_line_user,garment.jobwork.sent.line.user,model_garment_jobwork_sent_line,group_garment_user,1,0,0,0
access_garment_jobwork_sent_line_admin,garment.jobwork.sent.line.admin,model_garment_jobwork_sent_line,group_garment_admin,1,1,1,1
access_garment_jobwork_inward_line_user,garment.jobwork.inward.line.user,model_garment_jobwork_inward_line,group_garment_user,1,0,0,0
access_garment_jobwork_inward_line_admin,garment.jobwork.inward.line.admin,model_garment_jobwork_inward_line,group_garment_admin,1,1,1,1
access_garment_defect_type_user,garment.defect.type.user,model_garment_defect_type,group_garment_user,1,0,0,0
access_garment_defect_type_admin,garment.defect.type.admin,model_garment_defect_type,group_garment_admin,1,1,1,1
access_garment_quality_inspection_user,garment.quality.inspection.user,model_garment_quality_inspection,group_garment_user,1,0,0,0
access_garment_quality_inspection_qc,garment.quality.inspection.qc,model_garment_quality_inspection,group_garment_quality,1,1,1,1
access_garment_quality_inspection_admin,garment.quality.inspection.admin,model_garment_quality_inspection,group_garment_admin,1,1,1,1
access_garment_quality_defect_line_user,garment.quality.defect.line.user,model_garment_quality_defect_line,group_garment_user,1,0,0,0
access_garment_quality_defect_line_admin,garment.quality.defect.line.admin,model_garment_quality_defect_line,group_garment_admin,1,1,1,1
access_garment_rework_order_user,garment.rework.order.user,model_garment_rework_order,group_garment_user,1,0,0,0
access_garment_rework_order_qc,garment.rework.order.qc,model_garment_rework_order,group_garment_quality,1,1,1,1
access_garment_rework_order_admin,garment.rework.order.admin,model_garment_rework_order,group_garment_admin,1,1,1,1
access_garment_packing_plan_user,garment.packing.plan.user,model_garment_packing_plan,group_garment_user,1,0,0,0
access_garment_packing_plan_pck,garment.packing.plan.pck,model_garment_packing_plan,group_garment_packing,1,1,1,1
access_garment_packing_plan_admin,garment.packing.plan.admin,model_garment_packing_plan,group_garment_admin,1,1,1,1
access_garment_carton_user,garment.carton.user,model_garment_carton,group_garment_user,1,0,0,0
access_garment_carton_pck,garment.carton.pck,model_garment_carton,group_garment_packing,1,1,1,1
access_garment_carton_admin,garment.carton.admin,model_garment_carton,group_garment_admin,1,1,1,1
access_garment_carton_line_user,garment.carton.line.user,model_garment_carton_line,group_garment_user,1,0,0,0
access_garment_carton_line_admin,garment.carton.line.admin,model_garment_carton_line,group_garment_admin,1,1,1,1
access_garment_finished_goods_user,garment.finished.goods.user,model_garment_finished_goods,group_garment_user,1,0,0,0
access_garment_finished_goods_inv,garment.finished.goods.inv,model_garment_finished_goods,group_garment_inventory,1,1,1,1
access_garment_finished_goods_admin,garment.finished.goods.admin,model_garment_finished_goods,group_garment_admin,1,1,1,1
access_garment_dispatch_user,garment.dispatch.user,model_garment_dispatch,group_garment_user,1,0,0,0
access_garment_dispatch_pck,garment.dispatch.pck,model_garment_dispatch,group_garment_packing,1,1,1,1
access_garment_dispatch_admin,garment.dispatch.admin,model_garment_dispatch,group_garment_admin,1,1,1,1
access_garment_costing_user,garment.costing.user,model_garment_costing,group_garment_user,1,0,0,0
access_garment_costing_merch,garment.costing.merch,model_garment_costing,group_garment_merchandiser,1,1,1,1
access_garment_costing_admin,garment.costing.admin,model_garment_costing,group_garment_admin,1,1,1,1
access_garment_dashboard_user,garment.dashboard.user,model_garment_dashboard,group_garment_user,1,0,0,0
access_garment_dashboard_admin,garment.dashboard.admin,model_garment_dashboard,group_garment_admin,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_garment_brand_user garment.brand.user model_garment_brand group_garment_user 1 0 0 0
3 access_garment_brand_admin garment.brand.admin model_garment_brand group_garment_admin 1 1 1 1
4 access_garment_season_user garment.season.user model_garment_season group_garment_user 1 0 0 0
5 access_garment_season_admin garment.season.admin model_garment_season group_garment_admin 1 1 1 1
6 access_garment_gender_user garment.gender.user model_garment_gender group_garment_user 1 0 0 0
7 access_garment_gender_admin garment.gender.admin model_garment_gender group_garment_admin 1 1 1 1
8 access_garment_type_user garment.type.user model_garment_type group_garment_user 1 0 0 0
9 access_garment_type_admin garment.type.admin model_garment_type group_garment_admin 1 1 1 1
10 access_garment_fabric_type_user garment.fabric.type.user model_garment_fabric_type group_garment_user 1 0 0 0
11 access_garment_fabric_type_admin garment.fabric.type.admin model_garment_fabric_type group_garment_admin 1 1 1 1
12 access_garment_composition_user garment.composition.user model_garment_composition group_garment_user 1 0 0 0
13 access_garment_composition_admin garment.composition.admin model_garment_composition group_garment_admin 1 1 1 1
14 access_garment_color_user garment.color.user model_garment_color group_garment_user 1 0 0 0
15 access_garment_color_admin garment.color.admin model_garment_color group_garment_admin 1 1 1 1
16 access_garment_size_user garment.size.user model_garment_size group_garment_user 1 0 0 0
17 access_garment_size_admin garment.size.admin model_garment_size group_garment_admin 1 1 1 1
18 access_garment_style_user garment.style.user model_garment_style group_garment_user 1 0 0 0
19 access_garment_style_merch garment.style.merch model_garment_style group_garment_merchandiser 1 1 1 1
20 access_garment_style_admin garment.style.admin model_garment_style group_garment_admin 1 1 1 1
21 access_garment_style_measurement_user garment.style.measurement.user model_garment_style_measurement group_garment_user 1 0 0 0
22 access_garment_style_measurement_admin garment.style.measurement.admin model_garment_style_measurement group_garment_admin 1 1 1 1
23 access_garment_order_matrix_line_user garment.order.matrix.line.user model_garment_order_matrix_line group_garment_user 1 0 0 0
24 access_garment_order_matrix_line_merch garment.order.matrix.line.merch model_garment_order_matrix_line group_garment_merchandiser 1 1 1 1
25 access_garment_order_matrix_line_admin garment.order.matrix.line.admin model_garment_order_matrix_line group_garment_admin 1 1 1 1
26 access_garment_bom_user garment.bom.user model_garment_bom group_garment_user 1 0 0 0
27 access_garment_bom_prod garment.bom.prod model_garment_bom group_garment_production 1 1 1 1
28 access_garment_bom_admin garment.bom.admin model_garment_bom group_garment_admin 1 1 1 1
29 access_garment_bom_fabric_user garment.bom.fabric.user model_garment_bom_fabric group_garment_user 1 0 0 0
30 access_garment_bom_fabric_admin garment.bom.fabric.admin model_garment_bom_fabric group_garment_admin 1 1 1 1
31 access_garment_bom_trim_user garment.bom.trim.user model_garment_bom_trim group_garment_user 1 0 0 0
32 access_garment_bom_trim_admin garment.bom.trim.admin model_garment_bom_trim group_garment_admin 1 1 1 1
33 access_garment_bom_operation_user garment.bom.operation.user model_garment_bom_operation group_garment_user 1 0 0 0
34 access_garment_bom_operation_admin garment.bom.operation.admin model_garment_bom_operation group_garment_admin 1 1 1 1
35 access_garment_production_plan_user garment.production.plan.user model_garment_production_plan group_garment_user 1 0 0 0
36 access_garment_production_plan_prod garment.production.plan.prod model_garment_production_plan group_garment_production 1 1 1 1
37 access_garment_production_plan_admin garment.production.plan.admin model_garment_production_plan group_garment_admin 1 1 1 1
38 access_garment_production_plan_line_user garment.production.plan.line.user model_garment_production_plan_line group_garment_user 1 0 0 0
39 access_garment_production_plan_line_admin garment.production.plan.line.admin model_garment_production_plan_line group_garment_admin 1 1 1 1
40 access_garment_fabric_roll_user garment.fabric.roll.user model_garment_fabric_roll group_garment_user 1 0 0 0
41 access_garment_fabric_roll_inv garment.fabric.roll.inv model_garment_fabric_roll group_garment_inventory 1 1 1 1
42 access_garment_fabric_roll_admin garment.fabric.roll.admin model_garment_fabric_roll group_garment_admin 1 1 1 1
43 access_garment_fabric_inspection_user garment.fabric.inspection.user model_garment_fabric_inspection group_garment_user 1 0 0 0
44 access_garment_fabric_inspection_qc garment.fabric.inspection.qc model_garment_fabric_inspection group_garment_quality 1 1 1 1
45 access_garment_fabric_inspection_admin garment.fabric.inspection.admin model_garment_fabric_inspection group_garment_admin 1 1 1 1
46 access_garment_mrp_requirement_user garment.mrp.requirement.user model_garment_mrp_requirement group_garment_user 1 0 0 0
47 access_garment_mrp_requirement_prod garment.mrp.requirement.prod model_garment_mrp_requirement group_garment_production 1 1 1 1
48 access_garment_mrp_requirement_admin garment.mrp.requirement.admin model_garment_mrp_requirement group_garment_admin 1 1 1 1
49 access_garment_mrp_requirement_line_user garment.mrp.requirement.line.user model_garment_mrp_requirement_line group_garment_user 1 0 0 0
50 access_garment_mrp_requirement_line_admin garment.mrp.requirement.line.admin model_garment_mrp_requirement_line group_garment_admin 1 1 1 1
51 access_garment_cutting_plan_user garment.cutting.plan.user model_garment_cutting_plan group_garment_user 1 0 0 0
52 access_garment_cutting_plan_prod garment.cutting.plan.prod model_garment_cutting_plan group_garment_production 1 1 1 1
53 access_garment_cutting_plan_admin garment.cutting.plan.admin model_garment_cutting_plan group_garment_admin 1 1 1 1
54 access_garment_cutting_ratio_line_user garment.cutting.ratio.line.user model_garment_cutting_ratio_line group_garment_user 1 0 0 0
55 access_garment_cutting_ratio_line_admin garment.cutting.ratio.line.admin model_garment_cutting_ratio_line group_garment_admin 1 1 1 1
56 access_garment_cutting_roll_line_user garment.cutting.roll.line.user model_garment_cutting_roll_line group_garment_user 1 0 0 0
57 access_garment_cutting_roll_line_admin garment.cutting.roll.line.admin model_garment_cutting_roll_line group_garment_admin 1 1 1 1
58 access_garment_bundle_user garment.bundle.user model_garment_bundle group_garment_user 1 0 0 0
59 access_garment_bundle_prod garment.bundle.prod model_garment_bundle group_garment_production 1 1 1 1
60 access_garment_bundle_admin garment.bundle.admin model_garment_bundle group_garment_admin 1 1 1 1
61 access_garment_bundle_history_user garment.bundle.history.user model_garment_bundle_history group_garment_user 1 0 0 0
62 access_garment_bundle_history_admin garment.bundle.history.admin model_garment_bundle_history group_garment_admin 1 1 1 1
63 access_garment_sewing_line_user garment.sewing.line.user model_garment_sewing_line group_garment_user 1 0 0 0
64 access_garment_sewing_line_prod garment.sewing.line.prod model_garment_sewing_line group_garment_production 1 1 1 1
65 access_garment_sewing_line_admin garment.sewing.line.admin model_garment_sewing_line group_garment_admin 1 1 1 1
66 access_garment_sewing_hourly_user garment.sewing.hourly.user model_garment_sewing_hourly group_garment_user 1 0 0 0
67 access_garment_sewing_hourly_prod garment.sewing.hourly.prod model_garment_sewing_hourly group_garment_production 1 1 1 1
68 access_garment_sewing_hourly_admin garment.sewing.hourly.admin model_garment_sewing_hourly group_garment_admin 1 1 1 1
69 access_garment_jobwork_order_user garment.jobwork.order.user model_garment_jobwork_order group_garment_user 1 0 0 0
70 access_garment_jobwork_order_jw garment.jobwork.order.jw model_garment_jobwork_order group_garment_jobwork 1 1 1 1
71 access_garment_jobwork_order_admin garment.jobwork.order.admin model_garment_jobwork_order group_garment_admin 1 1 1 1
72 access_garment_jobwork_sent_line_user garment.jobwork.sent.line.user model_garment_jobwork_sent_line group_garment_user 1 0 0 0
73 access_garment_jobwork_sent_line_admin garment.jobwork.sent.line.admin model_garment_jobwork_sent_line group_garment_admin 1 1 1 1
74 access_garment_jobwork_inward_line_user garment.jobwork.inward.line.user model_garment_jobwork_inward_line group_garment_user 1 0 0 0
75 access_garment_jobwork_inward_line_admin garment.jobwork.inward.line.admin model_garment_jobwork_inward_line group_garment_admin 1 1 1 1
76 access_garment_defect_type_user garment.defect.type.user model_garment_defect_type group_garment_user 1 0 0 0
77 access_garment_defect_type_admin garment.defect.type.admin model_garment_defect_type group_garment_admin 1 1 1 1
78 access_garment_quality_inspection_user garment.quality.inspection.user model_garment_quality_inspection group_garment_user 1 0 0 0
79 access_garment_quality_inspection_qc garment.quality.inspection.qc model_garment_quality_inspection group_garment_quality 1 1 1 1
80 access_garment_quality_inspection_admin garment.quality.inspection.admin model_garment_quality_inspection group_garment_admin 1 1 1 1
81 access_garment_quality_defect_line_user garment.quality.defect.line.user model_garment_quality_defect_line group_garment_user 1 0 0 0
82 access_garment_quality_defect_line_admin garment.quality.defect.line.admin model_garment_quality_defect_line group_garment_admin 1 1 1 1
83 access_garment_rework_order_user garment.rework.order.user model_garment_rework_order group_garment_user 1 0 0 0
84 access_garment_rework_order_qc garment.rework.order.qc model_garment_rework_order group_garment_quality 1 1 1 1
85 access_garment_rework_order_admin garment.rework.order.admin model_garment_rework_order group_garment_admin 1 1 1 1
86 access_garment_packing_plan_user garment.packing.plan.user model_garment_packing_plan group_garment_user 1 0 0 0
87 access_garment_packing_plan_pck garment.packing.plan.pck model_garment_packing_plan group_garment_packing 1 1 1 1
88 access_garment_packing_plan_admin garment.packing.plan.admin model_garment_packing_plan group_garment_admin 1 1 1 1
89 access_garment_carton_user garment.carton.user model_garment_carton group_garment_user 1 0 0 0
90 access_garment_carton_pck garment.carton.pck model_garment_carton group_garment_packing 1 1 1 1
91 access_garment_carton_admin garment.carton.admin model_garment_carton group_garment_admin 1 1 1 1
92 access_garment_carton_line_user garment.carton.line.user model_garment_carton_line group_garment_user 1 0 0 0
93 access_garment_carton_line_admin garment.carton.line.admin model_garment_carton_line group_garment_admin 1 1 1 1
94 access_garment_finished_goods_user garment.finished.goods.user model_garment_finished_goods group_garment_user 1 0 0 0
95 access_garment_finished_goods_inv garment.finished.goods.inv model_garment_finished_goods group_garment_inventory 1 1 1 1
96 access_garment_finished_goods_admin garment.finished.goods.admin model_garment_finished_goods group_garment_admin 1 1 1 1
97 access_garment_dispatch_user garment.dispatch.user model_garment_dispatch group_garment_user 1 0 0 0
98 access_garment_dispatch_pck garment.dispatch.pck model_garment_dispatch group_garment_packing 1 1 1 1
99 access_garment_dispatch_admin garment.dispatch.admin model_garment_dispatch group_garment_admin 1 1 1 1
100 access_garment_costing_user garment.costing.user model_garment_costing group_garment_user 1 0 0 0
101 access_garment_costing_merch garment.costing.merch model_garment_costing group_garment_merchandiser 1 1 1 1
102 access_garment_costing_admin garment.costing.admin model_garment_costing group_garment_admin 1 1 1 1
103 access_garment_dashboard_user garment.dashboard.user model_garment_dashboard group_garment_user 1 0 0 0
104 access_garment_dashboard_admin garment.dashboard.admin model_garment_dashboard group_garment_admin 1 1 1 1

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import test_e2e_lifecycle

View File

@ -0,0 +1,564 @@
# -*- coding: utf-8 -*-
"""
Tiruppur Garment Manufacturing ERP - Standalone End-to-End Functional Test Suite
Validates the complete lifecycle on Odoo 19 without requiring any demo or static XML data:
1. Dynamic Master Data (Brands, Types, Fabrics, Sizes, Colors, Lines, Defect Types)
2. Garment Style & Product Variant Generation
3. Sales Order with 2D Size x Color Matrix Grid & Sync to order lines
4. Garment BOM & Fabric Consumption (with Cutting Wastage)
5. Fabric Roll Inventory & ASTM D5430 4-Point Inspection Scoring
6. Cutting Order, Lay Planning & Automated Bundle Generation
7. Sewing Line Floor Logging & Hourly Efficiency % Formula
8. Subcontracting / Job Work GST Delivery Challan & Process Loss Audit
9. Quality Control Inspection & Automatic Rework Order Flow
10. Export Carton Packing (Ratio & Solid) with CBM Calculation & Finished Goods Receipt
11. Dispatch Order & Finished Goods Inventory Deduction
12. Pre-Costing Sheet & Commercial Margin Analysis
"""
import logging
from odoo.tests.common import TransactionCase, tagged
_logger = logging.getLogger(__name__)
@tagged('post_install', '-at_install', 'garment_erp')
class TestGarmentERPLifecycle(TransactionCase):
@classmethod
def setUpClass(cls):
super(TestGarmentERPLifecycle, cls).setUpClass()
# 1. Dynamic Master Data
cls.brand = cls.env['garment.brand'].create({'name': 'Test Garment Brand', 'code': 'T-BRD'})
cls.gtype = cls.env['garment.type'].create({'name': 'T-Shirt', 'code': 'T-TSHIRT', 'category': 'top'})
cls.ftype = cls.env['garment.fabric.type'].create({'name': 'Single Jersey', 'code': 'T-SJ', 'structure': 'single_jersey'})
cls.comp = cls.env['garment.composition'].create({'name': '100% Cotton', 'code': 'T-100COT', 'cotton_pct': 100.0})
cls.color_blk = cls.env['garment.color'].create({'name': 'Test Black', 'code': 'T-BLK', 'color_hex': '#000000'})
cls.color_wht = cls.env['garment.color'].create({'name': 'Test White', 'code': 'T-WHT', 'color_hex': '#FFFFFF'})
cls.size_m = cls.env['garment.size'].create({'name': 'M', 'code': 'M', 'sequence': 2})
cls.size_l = cls.env['garment.size'].create({'name': 'L', 'code': 'L', 'sequence': 3})
cls.line = cls.env['garment.sewing.line'].create({
'name': 'Test Sewing Line 01',
'code': 'T-LINE-01',
'operator_count': 20,
'helper_count': 4,
'daily_capacity_pcs': 800,
})
cls.defect = cls.env['garment.defect.type'].create({
'name': 'Skip Stitch',
'code': 'DEF-SS-01',
'category': 'stitching',
'severity': 'major',
})
# Dynamic Partners
cls.buyer = cls.env['res.partner'].create({
'name': 'Test Buyer International Ltd',
'customer_rank': 1,
})
cls.supplier = cls.env['res.partner'].create({
'name': 'Test Subcontractor Dyeing & Washing',
'supplier_rank': 1,
})
# Raw Material Product
cls.raw_fabric = cls.env['product.product'].create({
'name': 'Test Single Jersey 180 GSM Fabric',
'type': 'consu',
})
# 2. Dynamic Garment Style
cls.style = cls.env['garment.style'].create({
'name': 'TEST-STYLE-001',
'style_name': 'Test Crew Neck T-Shirt',
'garment_type_id': cls.gtype.id,
'fabric_type_id': cls.ftype.id,
'composition_id': cls.comp.id,
'brand_id': cls.brand.id,
'sam': 12.0,
'gsm': 180,
'color_ids': [(6, 0, [cls.color_blk.id, cls.color_wht.id])],
'size_ids': [(6, 0, [cls.size_m.id, cls.size_l.id])],
})
def test_01_dynamic_master_data(self):
"""Verify dynamic master data creation and attributes"""
self.assertEqual(self.color_blk.code, 'T-BLK')
self.assertEqual(self.size_m.code, 'M')
self.assertEqual(self.line.operator_count, 20)
self.assertEqual(self.defect.category, 'stitching')
_logger.info("PASS: Test 01 - Dynamic Master Data verified successfully.")
def test_02_garment_style_and_variants(self):
"""Test garment style and automatic variant generation"""
self.assertEqual(self.style.sam, 12.0, "Sewing SAM should be 12.0 mins")
self.style.action_generate_product_variants()
self.assertTrue(self.style.variant_count >= 4, "4 product variants (2 colors x 2 sizes) should be generated")
_logger.info("PASS: Test 02 - Garment Style & Variants (%s variants) verified.", self.style.variant_count)
def test_03_sales_matrix_sync(self):
"""Test sales order matrix lines synchronization with standard sale.order.line"""
so = self.env['sale.order'].create({
'partner_id': self.buyer.id,
'buyer_po_ref': 'PO-TEST-1001',
'garment_style_id': self.style.id,
'order_matrix_line_ids': [
(0, 0, {
'color_id': self.color_blk.id,
'size_id': self.size_m.id,
'quantity': 500,
'unit_price': 150.0,
}),
(0, 0, {
'color_id': self.color_blk.id,
'size_id': self.size_l.id,
'quantity': 500,
'unit_price': 150.0,
}),
]
})
self.assertEqual(so.total_garment_qty, 1000, "Total quantity in matrix should be 1,000 pcs")
# Verify sync action
so.action_sync_matrix_to_order_lines()
self.assertEqual(len(so.order_line), 2, "Order lines should be synchronized from matrix")
self.assertEqual(sum(so.order_line.mapped('product_uom_qty')), 1000.0)
_logger.info("PASS: Test 03 - Sales Order 2D Matrix & Sync verified.")
def test_04_garment_bom(self):
"""Test Garment BOM fabric consumption & labor costs"""
bom = self.env['garment.bom'].create({
'style_id': self.style.id,
'fabric_line_ids': [
(0, 0, {
'component_type': 'body',
'product_id': self.raw_fabric.id,
'consumption_kg': 0.22,
'wastage_pct': 5.0,
'cost_per_kg': 320.0,
})
],
'operation_line_ids': [
(0, 0, {
'operation_name': 'Sewing Assembly',
'department': 'sewing',
'machine_type': 'snls',
'sam': 12.0,
'minute_rate': 2.5,
})
]
})
self.assertTrue(bom.total_fabric_cost > 0, "Total fabric cost must be calculated")
self.assertTrue(bom.total_unit_cost > 0, "Total unit cost must include fabric and operations")
_logger.info("PASS: Test 04 - Garment BOM consumption (Total Cost: %.2f INR) verified.",
bom.total_unit_cost)
def test_05_fabric_roll_4point_inspection(self):
"""Test Fabric Roll 4-Point System score calculation and pass/fail verdict"""
new_roll = self.env['garment.fabric.roll'].create({
'product_id': self.raw_fabric.id,
'shade_lot': 'LOT-TEST-01',
'gross_weight': 25.0,
'tare_weight': 0.8,
'length_meters': 80.0,
'width': 30.0,
'gsm': 180,
})
self.assertEqual(new_roll.net_weight, 24.2)
# Inspection: 4-Point formula
inspection = self.env['garment.fabric.inspection'].create({
'roll_id': new_roll.id,
'inspected_meters': 30.0,
'actual_width': 30.0,
'defects_1pt_count': 2,
'defects_2pt_count': 1,
'defects_3pt_count': 0,
'defects_4pt_count': 0,
})
self.assertEqual(inspection.total_defect_points, 4) # 2*1 + 1*2 = 4
self.assertTrue(inspection.score_per_100_sqm > 0)
inspection.action_pass()
self.assertEqual(inspection.state, 'passed')
self.assertEqual(new_roll.status, 'approved')
_logger.info("PASS: Test 05 - 4-Point Inspection (Score: %.2f pts/100m², Verdict: %s) verified.",
inspection.score_per_100_sqm, inspection.state)
def test_06_cutting_and_bundle_generation(self):
"""Test Cutting Plan lay plies and automatic bundle generation"""
prod_plan = self.env['garment.production.plan'].create({
'style_id': self.style.id,
'planned_qty': 400,
'date_start': '2026-09-20',
})
cut = self.env['garment.cutting.plan'].create({
'production_plan_id': prod_plan.id,
'color_id': self.color_blk.id,
'marker_no': 'MKR-TEST-01',
'marker_length': 6.2,
'plies_count': 100,
'bundle_size': 25,
'ratio_line_ids': [
(0, 0, {'size_id': self.size_m.id, 'ratio': 2}),
(0, 0, {'size_id': self.size_l.id, 'ratio': 2}),
]
})
# 100 plies * (2 + 2) ratio = 400 pieces planned
self.assertEqual(cut.planned_cut_qty, 400)
# Generate bundles
cut.action_generate_bundles()
self.assertEqual(len(cut.bundle_ids), 16)
bundle = cut.bundle_ids[0]
self.assertTrue(bundle.barcode, "Bundle must have a barcode")
self.assertEqual(bundle.stage, 'sewing')
_logger.info("PASS: Test 06 - Cutting Plan & %s Serialized Bundles generated.", len(cut.bundle_ids))
def test_07_sewing_line_hourly_efficiency(self):
"""Test Sewing Line 8-Hour Floor Production logging and efficiency % formula"""
log = self.env['garment.sewing.hourly'].create({
'line_id': self.line.id,
'style_id': self.style.id,
'sam': 12.0,
'operators_present': 20,
'total_worked_hours': 8.0,
'h1_actual': 100,
'h2_actual': 105,
'h3_actual': 110,
'h4_actual': 115,
'h5_actual': 105,
'h6_actual': 100,
'h7_actual': 95,
'h8_actual': 90,
})
self.assertEqual(log.total_actual, 820)
# Efficiency % formula: (Actual Output * SAM) / (Operators * Hours * 60) * 100
expected_eff = round((820 * 12.0) / (20 * 8.0 * 60.0) * 100.0, 2)
self.assertEqual(log.efficiency_pct, expected_eff)
_logger.info("PASS: Test 07 - Sewing Line Output: %s pcs, Efficiency: %.2f%% verified.",
log.total_actual, log.efficiency_pct)
def test_08_jobwork_delivery_challan_process_loss(self):
"""Test Subcontracting / Job Work GST DC and Process Loss % Audit"""
jw = self.env['garment.jobwork.order'].create({
'vendor_id': self.supplier.id,
'process_type': 'dyeing',
'rate_per_unit': 45.0,
'allowed_process_loss_pct': 4.0,
'sent_line_ids': [(0, 0, {
'description': 'Greige Single Jersey Fabric for Bio-Polishing & Dyeing',
'quantity': 1000.0,
'uom': 'Kg',
})],
})
self.assertEqual(jw.total_sent_qty, 1000.0)
jw.action_dispatch_material()
self.assertEqual(jw.state, 'sent')
# Record inward receipt (990 kg received, 10 kg rejected)
jw.write({
'inward_line_ids': [(0, 0, {
'vendor_dc_no': 'DC-VENDOR-99',
'received_qty': 990.0,
'rejected_qty': 10.0,
})],
'state': 'completed',
})
self.assertEqual(jw.total_received_qty, 990.0)
self.assertEqual(jw.total_accepted_qty, 980.0)
# Process loss: (1000 - 990) / 1000 * 100 = 1.0%
self.assertEqual(jw.actual_process_loss_pct, 1.0)
_logger.info("PASS: Test 08 - Job Work Subcontracting & Process Loss (%.1f%%) verified.", jw.actual_process_loss_pct)
def test_09_quality_control_automatic_rework(self):
"""Test Quality Inspection failure and automatic Rework Order creation"""
qc = self.env['garment.quality.inspection'].create({
'inspection_stage': 'sewing_endline',
'sample_size': 50,
'passed_qty': 46,
'failed_qty': 4,
'rework_qty': 4,
'verdict': 'rework_needed',
'defect_line_ids': [(0, 0, {
'defect_type_id': self.defect.id,
'defect_count': 4,
})],
})
self.assertEqual(qc.failed_qty, 4)
# Trigger automatic rework order
qc.action_trigger_rework()
self.assertTrue(qc.rework_count > 0, "Rework order must be automatically created")
rework = qc.rework_order_ids[0]
self.assertEqual(rework.rework_qty, 4)
self.assertEqual(rework.state, 'assigned')
# Run rework workflow
rework.action_start_rework()
self.assertEqual(rework.state, 'in_rework')
rework.action_ready_recheck()
self.assertEqual(rework.state, 'ready_recheck')
rework.action_pass_recheck()
self.assertEqual(rework.state, 'passed')
self.assertEqual(rework.passed_rework_qty, 4)
_logger.info("PASS: Test 09 - Quality Inspection & Auto Rework (Passed 4 pcs) verified.")
def test_10_carton_packing_finished_goods_dispatch(self):
"""Test Ratio Carton packing, CBM, Finished Goods receipt, and Dispatch deduction"""
prod_plan = self.env['garment.production.plan'].create({
'style_id': self.style.id,
'planned_qty': 100,
})
# Create packing plan
plan = self.env['garment.packing.plan'].create({
'production_plan_id': prod_plan.id,
'style_id': self.style.id,
'packing_type': 'solid_color_solid_size',
'target_pack_qty': 100,
})
carton = self.env['garment.carton'].create({
'packing_plan_id': plan.id,
'carton_no': 1,
'length_cm': 60.0,
'width_cm': 40.0,
'height_cm': 30.0,
'line_ids': [(0, 0, {
'color_id': self.color_blk.id,
'size_id': self.size_m.id,
'quantity': 100,
})]
})
self.assertEqual(carton.total_pieces, 100)
# CBM: (60 * 40 * 30) / 1,000,000 = 0.072 m³
self.assertEqual(carton.cbm, 0.072)
self.assertEqual(plan.total_packed_qty, 100)
# Transfer to Finished Goods
plan.action_transfer_to_finished_goods()
self.assertEqual(plan.state, 'completed')
fg = self.env['garment.finished.goods'].search([
('style_id', '=', self.style.id),
('color_id', '=', self.color_blk.id),
('size_id', '=', self.size_m.id),
], limit=1)
self.assertTrue(fg, "Finished Goods stock record must exist")
self.assertEqual(fg.on_hand_qty, 100)
# Create Dispatch
dispatch = self.env['garment.dispatch'].create({
'customer_id': self.buyer.id,
'carrier_name': 'Maersk Line',
'container_no': 'MSKU-889900-1',
'seal_no': 'SL-77661',
})
carton.dispatch_id = dispatch.id
self.assertEqual(dispatch.total_pieces, 100)
self.assertEqual(dispatch.total_cartons, 1)
# Dispatch shipment
dispatch.action_dispatch()
self.assertEqual(dispatch.state, 'dispatched')
# Check stock deduction
fg.invalidate_recordset()
self.assertEqual(fg.shipped_qty, 100)
self.assertEqual(fg.on_hand_qty, 0)
_logger.info("PASS: Test 10 - Export Packing (CBM: %.3f m³), FG & Dispatch verified.", carton.cbm)
def test_11_garment_costing_margins(self):
"""Test Garment Costing Sheet and Profit Margin % calculation"""
costing = self.env['garment.costing'].create({
'style_id': self.style.id,
'target_selling_price': 180.0,
'fabric_cost_per_pc': 65.0,
'trims_accessories_cost_per_pc': 18.0,
'sewing_cm_cost_per_pc': 28.0,
})
self.assertEqual(costing.target_selling_price, 180.0)
self.assertTrue(costing.total_cost_per_pc > 0, "Total cost must be calculated")
self.assertTrue(costing.gross_margin_amount > 0, "Gross margin amount must be > 0")
self.assertTrue(costing.gross_margin_pct > 0, "Gross margin % must be > 0")
_logger.info("PASS: Test 11 - Garment Costing (Cost: %.2f INR, Margin: %.2f%%) verified.",
costing.total_cost_per_pc, costing.gross_margin_pct)
def test_12_garment_mrp_requirements(self):
"""Test MRP Material Explosion, Shortage Calculation and Purchase RFQ Generation"""
prod_plan = self.env['garment.production.plan'].create({
'style_id': self.style.id,
'planned_qty': 1000,
'date_start': '2026-09-20',
'date_delivery': '2026-10-05',
})
bom = self.env['garment.bom'].create({
'style_id': self.style.id,
'base_qty': 1.0,
'fabric_line_ids': [
(0, 0, {
'component_type': 'body',
'product_id': self.raw_fabric.id,
'consumption_kg': 0.22,
'wastage_pct': 5.0,
'cost_per_kg': 320.0,
})
],
'trim_line_ids': [
(0, 0, {
'item_name': 'Woven Main Label',
'unit_cost': 1.5,
'consumption_qty': 1.0,
'wastage_pct': 2.0,
})
]
})
mrp = self.env['garment.mrp.requirement'].create({
'production_plan_id': prod_plan.id,
'bom_id': bom.id,
'order_qty': 1000,
})
mrp.action_compute_requirements()
self.assertEqual(mrp.state, 'calculated')
self.assertTrue(len(mrp.line_ids) >= 2, "MRP must have lines for fabric and trim")
self.assertTrue(mrp.total_shortage_cost > 0, "Shortage cost must be computed")
# Generate Purchase RFQs
mrp.action_generate_purchase_rfqs()
self.assertEqual(mrp.state, 'rfq_created')
self.assertTrue(mrp.purchase_count > 0, "At least 1 Purchase RFQ must be generated")
po = mrp.purchase_order_ids[0]
self.assertEqual(po.state, 'draft')
_logger.info("PASS: Test 12 - MRP Requirements (%s lines, RFQ %s) verified.", len(mrp.line_ids), po.name)
def test_13_live_factory_dashboard_kpis(self):
"""Test Live Factory Dashboard KPI Aggregations"""
dashboard = self.env['garment.dashboard'].create({'name': 'Test Control Room'})
dashboard._compute_kpis()
self.assertIsNotNone(dashboard.total_sales_orders)
self.assertIsNotNone(dashboard.avg_line_efficiency)
self.assertIsNotNone(dashboard.total_fabric_stock_kg)
_logger.info("PASS: Test 13 - Factory Dashboard KPIs verified.")
def test_14_all_9_qweb_reports_rendering(self):
"""Test rendering of all 9 industrial QWeb PDF report templates"""
# 1. Costing
costing = self.env['garment.costing'].create({
'style_id': self.style.id,
'target_selling_price': 220.0,
'fabric_cost_per_pc': 75.0,
'trims_accessories_cost_per_pc': 22.0,
'sewing_cm_cost_per_pc': 35.0,
'washing_cost_per_pc': 12.0,
'factory_overhead_pct': 8.0,
})
# 2. Sales Order with Matrix
so = self.env['sale.order'].create({
'partner_id': self.buyer.id,
'garment_style_id': self.style.id,
'buyer_po_ref': 'PO-TEST-REP-01',
'order_matrix_line_ids': [
(0, 0, {'color_id': self.color_blk.id, 'size_id': self.size_m.id, 'quantity': 200, 'unit_price': 200.0}),
]
})
so.action_sync_matrix_to_order_lines()
# 3. Production Plan
prod_plan = self.env['garment.production.plan'].create({
'style_id': self.style.id,
'planned_qty': 400,
'date_start': '2026-09-20',
'date_delivery': '2026-10-05',
})
# 4. Cutting Plan & Bundles
cut = self.env['garment.cutting.plan'].create({
'production_plan_id': prod_plan.id,
'color_id': self.color_blk.id,
'marker_no': 'MKR-REP-01',
'marker_length': 6.0,
'plies_count': 50,
'bundle_size': 25,
'ratio_line_ids': [
(0, 0, {'size_id': self.size_m.id, 'ratio': 2}),
]
})
cut.action_generate_bundles()
bundle = cut.bundle_ids[0]
# 5. Fabric Roll & Inspection
roll = self.env['garment.fabric.roll'].create({
'product_id': self.raw_fabric.id,
'shade_lot': 'LOT-REP-01',
'gross_weight': 25.0,
'tare_weight': 0.8,
'length_meters': 80.0,
'width': 30.0,
'gsm': 180,
})
insp = self.env['garment.fabric.inspection'].create({
'roll_id': roll.id,
'inspected_meters': 25.0,
'actual_width': 30.0,
'defects_1pt_count': 1,
})
insp.action_pass()
# 6. Job Work Order
jw = self.env['garment.jobwork.order'].create({
'vendor_id': self.supplier.id,
'process_type': 'washing',
'rate_per_unit': 15.0,
'sent_line_ids': [(0, 0, {'description': 'Washing batch', 'quantity': 500.0, 'uom': 'Pcs'})],
})
# 7. Quality Inspection
qc = self.env['garment.quality.inspection'].create({
'inspection_stage': 'sewing_endline',
'sample_size': 50,
'passed_qty': 48,
'failed_qty': 2,
'defect_line_ids': [(0, 0, {'defect_type_id': self.defect.id, 'defect_count': 2})],
})
# 8. Packing Plan
pack = self.env['garment.packing.plan'].create({
'production_plan_id': prod_plan.id,
'style_id': self.style.id,
'packing_type': 'solid_color_assorted_size',
'target_pack_qty': 200,
})
carton = self.env['garment.carton'].create({
'packing_plan_id': pack.id,
'carton_no': 1,
'length_cm': 60.0,
'width_cm': 40.0,
'height_cm': 30.0,
'line_ids': [(0, 0, {'color_id': self.color_blk.id, 'size_id': self.size_m.id, 'quantity': 50})]
})
# Verify all 9 reports
reports = [
('garment_erp.report_sales_order_matrix_template', so),
('garment_erp.report_production_plan_template', prod_plan),
('garment_erp.report_bundle_barcode_template', bundle),
('garment_erp.report_fabric_inspection_template', insp),
('garment_erp.report_cutting_plan_template', cut),
('garment_erp.report_job_work_challan_template', jw),
('garment_erp.report_quality_inspection_template', qc),
('garment_erp.report_packing_list_template', pack),
('garment_erp.report_garment_costing_template', costing),
]
for rep_xmlid, record in reports:
report_action = self.env['ir.actions.report'].search([('report_name', '=', rep_xmlid)], limit=1)
self.assertTrue(report_action, f"Report action {rep_xmlid} must exist")
html = report_action._render_qweb_html(report_action.id, [record.id])[0]
self.assertTrue(len(html) > 500, f"Rendered HTML for {rep_xmlid} must be non-empty")
_logger.info("PASS: Report %s successfully rendered (%d bytes).", rep_xmlid, len(html))

View File

@ -0,0 +1,165 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Bundle Kanban View -->
<record id="view_garment_bundle_kanban" model="ir.ui.view">
<field name="name">garment.bundle.kanban</field>
<field name="model">garment.bundle</field>
<field name="arch" type="xml">
<kanban default_group_by="stage" sample="1">
<field name="id"/>
<field name="name"/>
<field name="barcode"/>
<field name="style_id"/>
<field name="color_id"/>
<field name="size_id"/>
<field name="quantity"/>
<field name="stage"/>
<field name="current_line_id"/>
<templates>
<t t-name="card">
<div class="p-2">
<div class="d-flex justify-content-between align-items-center mb-1">
<span class="fw-bold text-primary"><field name="name"/></span>
<span class="badge bg-dark"><field name="quantity"/> Pcs</span>
</div>
<div class="fw-semibold text-dark"><field name="style_id"/></div>
<div class="small text-muted mb-2">
<field name="color_id"/> · Size: <span class="badge bg-secondary"><field name="size_id"/></span>
</div>
<div class="small text-info" t-if="record.current_line_id.raw_value">
<i class="fa fa-cogs"/> <field name="current_line_id"/>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- Bundle List View -->
<record id="view_garment_bundle_list" model="ir.ui.view">
<field name="name">garment.bundle.list</field>
<field name="model">garment.bundle</field>
<field name="arch" type="xml">
<list string="Production Bundles" sample="1"
decoration-info="stage == 'cutting'"
decoration-warning="stage == 'sewing'"
decoration-primary="stage == 'jobwork'"
decoration-danger="stage == 'quality'"
decoration-success="stage in ['packing', 'completed']">
<field name="name"/>
<field name="barcode"/>
<field name="production_plan_id"/>
<field name="cutting_plan_id"/>
<field name="style_id"/>
<field name="color_id"/>
<field name="size_id"/>
<field name="quantity" sum="Total Pieces"/>
<field name="ply_start"/>
<field name="ply_end"/>
<field name="current_line_id"/>
<field name="stage" widget="badge"/>
<field name="status" widget="badge"/>
</list>
</field>
</record>
<!-- Bundle Form View -->
<record id="view_garment_bundle_form" model="ir.ui.view">
<field name="name">garment.bundle.form</field>
<field name="model">garment.bundle</field>
<field name="arch" type="xml">
<form string="Production Bundle">
<header>
<button name="action_move_to_sewing" string="Move to Sewing" type="object" class="btn-primary" invisible="stage != 'cutting'"/>
<button name="action_move_to_jobwork" string="Send to Job Work" type="object" class="btn-secondary" invisible="stage != 'sewing'"/>
<button name="action_move_to_quality" string="Send to Quality Audit" type="object" class="btn-warning" invisible="stage not in ['sewing', 'jobwork']"/>
<button name="action_move_to_packing" string="Quality Passed -> Packing" type="object" class="btn-success" invisible="stage != 'quality'"/>
<button name="action_complete_packing" string="Mark Packed into Carton" type="object" class="btn-success" invisible="stage != 'packing'"/>
<field name="stage" widget="statusbar"/>
</header>
<sheet>
<div class="oe_title">
<label for="name" string="Bundle Identifier"/>
<h1><field name="name"/></h1>
<h3>Barcode: <field name="barcode" class="text-primary"/></h3>
</div>
<group>
<group string="Bundle Content">
<field name="style_id"/>
<field name="color_id"/>
<field name="size_id"/>
<field name="quantity" class="fw-bold fs-4 text-success"/>
<label for="ply_start" string="Ply Serial Range"/>
<div class="o_row">
Ply <field name="ply_start"/> to <field name="ply_end"/>
</div>
</group>
<group string="Location &amp; Work Center">
<field name="production_plan_id"/>
<field name="cutting_plan_id"/>
<field name="current_line_id"/>
<field name="current_operator_id"/>
<field name="status" widget="badge"/>
</group>
</group>
<notebook>
<page string="Bundle Traveler History (Audit Trail)" name="history_page">
<field name="history_ids" readonly="1">
<list>
<field name="timestamp"/>
<field name="stage"/>
<field name="user_id"/>
<field name="sewing_line_id"/>
<field name="quantity"/>
<field name="remarks"/>
</list>
</field>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- Search View -->
<record id="view_garment_bundle_search" model="ir.ui.view">
<field name="name">garment.bundle.search</field>
<field name="model">garment.bundle</field>
<field name="arch" type="xml">
<search string="Search Bundles">
<field name="name"/>
<field name="barcode"/>
<field name="style_id"/>
<field name="color_id"/>
<field name="size_id"/>
<field name="current_line_id"/>
<separator/>
<filter string="In Cutting" name="stage_cutting" domain="[('stage', '=', 'cutting')]"/>
<filter string="In Sewing" name="stage_sewing" domain="[('stage', '=', 'sewing')]"/>
<filter string="In Job Work" name="stage_jobwork" domain="[('stage', '=', 'jobwork')]"/>
<filter string="In Quality" name="stage_quality" domain="[('stage', '=', 'quality')]"/>
<filter string="In Packing" name="stage_packing" domain="[('stage', '=', 'packing')]"/>
<group>
<filter string="Production Stage" name="group_stage" context="{'group_by': 'stage'}"/>
<filter string="Sewing Line" name="group_line" context="{'group_by': 'current_line_id'}"/>
<filter string="Garment Style" name="group_style" context="{'group_by': 'style_id'}"/>
<filter string="Color" name="group_color" context="{'group_by': 'color_id'}"/>
<filter string="Size" name="group_size" context="{'group_by': 'size_id'}"/>
</group>
</search>
</field>
</record>
<!-- Action & Menu -->
<record id="action_garment_bundle" model="ir.actions.act_window">
<field name="name">Production Bundles</field>
<field name="res_model">garment.bundle</field>
<field name="view_mode">kanban,list,form</field>
</record>
<menuitem id="menu_garment_bundle" name="Production Bundles" parent="menu_garment_cutting_root" action="action_garment_bundle" sequence="20"/>
</odoo>

View File

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Cutting Plan List View -->
<record id="view_garment_cutting_plan_list" model="ir.ui.view">
<field name="name">garment.cutting.plan.list</field>
<field name="model">garment.cutting.plan</field>
<field name="arch" type="xml">
<list string="Cutting Orders" sample="1"
decoration-info="state == 'planned'"
decoration-warning="state == 'cutting'"
decoration-success="state == 'completed'">
<field name="name"/>
<field name="production_plan_id"/>
<field name="style_id"/>
<field name="color_id"/>
<field name="plies_count"/>
<field name="planned_cut_qty" sum="Total Planned Pcs"/>
<field name="actual_cut_qty" sum="Total Actual Pcs"/>
<field name="total_fabric_used_kg" sum="Total Fabric Kg"/>
<field name="cutting_wastage_pct"/>
<field name="bundle_count" string="Bundles"/>
<field name="state" widget="badge"
decoration-info="state == 'planned'"
decoration-warning="state == 'cutting'"
decoration-success="state == 'completed'"/>
</list>
</field>
</record>
<!-- Cutting Plan Form View -->
<record id="view_garment_cutting_plan_form" model="ir.ui.view">
<field name="name">garment.cutting.plan.form</field>
<field name="model">garment.cutting.plan</field>
<field name="arch" type="xml">
<form string="Cutting Order &amp; Lay Plan">
<header>
<button name="action_start_cutting" string="Start Lay &amp; Cutting" type="object" class="btn-primary" invisible="state != 'draft'"/>
<button name="action_generate_bundles" string="Generate Production Bundles" type="object" class="btn-success" icon="fa-tags" invisible="state not in ['draft', 'planned', 'cutting']"/>
<field name="state" widget="statusbar" statusbar_visible="draft,planned,cutting,completed"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_view_bundles" type="object" class="oe_stat_button" icon="fa-tags">
<field name="bundle_count" widget="statinfo" string="Bundles"/>
</button>
</div>
<div class="oe_title">
<label for="name" string="Cutting Order Number"/>
<h1><field name="name"/></h1>
</div>
<group>
<group string="Order Details">
<field name="production_plan_id"/>
<field name="style_id"/>
<field name="color_id"/>
</group>
<group string="Marker &amp; Lay Parameters">
<field name="marker_no"/>
<field name="marker_length"/>
<field name="marker_width"/>
<field name="plies_count"/>
<field name="bundle_size"/>
</group>
</group>
<group string="Cutting Output &amp; Fabric Wastage">
<group>
<field name="planned_cut_qty" class="fw-bold"/>
<field name="actual_cut_qty" class="fw-bold fs-4 text-success"/>
</group>
<group>
<field name="total_fabric_used_kg"/>
<field name="end_bit_scrap_kg"/>
<field name="cutting_wastage_pct" widget="percentage" class="fw-bold text-danger"/>
</group>
</group>
<notebook>
<page string="1. Size Cut Ratios (Per Ply)" name="ratios_tab">
<field name="ratio_line_ids">
<list editable="bottom">
<field name="size_id"/>
<field name="ratio" sum="Total Ratio per Ply"/>
<field name="expected_cut_qty" sum="Total Expected Cut Pieces"/>
</list>
</field>
</page>
<page string="2. Fabric Rolls Allocated" name="rolls_tab">
<field name="roll_line_ids">
<list editable="bottom">
<field name="roll_id"/>
<field name="shade_lot"/>
<field name="weight_issued" sum="Total Issued Kg"/>
<field name="weight_used" sum="Total Consumed Kg"/>
<field name="end_bit_scrap" sum="Total Scrap Kg"/>
</list>
</field>
</page>
<page string="3. Generated Production Bundles" name="bundles_tab">
<field name="bundle_ids" readonly="1">
<list>
<field name="name"/>
<field name="barcode"/>
<field name="size_id"/>
<field name="quantity" sum="Total Pieces"/>
<field name="ply_start"/>
<field name="ply_end"/>
<field name="stage"/>
<field name="status"/>
</list>
</field>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- Action & Menu -->
<record id="action_garment_cutting_plan" model="ir.actions.act_window">
<field name="name">Cutting Orders</field>
<field name="res_model">garment.cutting.plan</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_garment_cutting_plan" name="Cutting Orders" parent="menu_garment_cutting_root" action="action_garment_cutting_plan" sequence="10"/>
</odoo>

View File

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Dispatch List View -->
<record id="view_garment_dispatch_list" model="ir.ui.view">
<field name="name">garment.dispatch.list</field>
<field name="model">garment.dispatch</field>
<field name="arch" type="xml">
<list string="Dispatch Orders" sample="1"
decoration-info="state == 'packed'"
decoration-warning="state == 'dispatched'"
decoration-success="state == 'delivered'">
<field name="name"/>
<field name="customer_id"/>
<field name="sale_order_id"/>
<field name="date_dispatch"/>
<field name="carrier_name"/>
<field name="bl_awb_no"/>
<field name="total_cartons" sum="Total Cartons"/>
<field name="total_pieces" sum="Total Pieces"/>
<field name="total_cbm" sum="Total CBM"/>
<field name="state" widget="badge"
decoration-info="state == 'packed'"
decoration-warning="state == 'dispatched'"
decoration-success="state == 'delivered'"/>
</list>
</field>
</record>
<!-- Dispatch Form View -->
<record id="view_garment_dispatch_form" model="ir.ui.view">
<field name="name">garment.dispatch.form</field>
<field name="model">garment.dispatch</field>
<field name="arch" type="xml">
<form string="Dispatch &amp; Delivery Note">
<header>
<button name="action_dispatch" string="Confirm Dispatch (Deduct FG Stock)" type="object" class="btn-primary" invisible="state not in ['draft', 'packed']"/>
<button name="action_mark_delivered" string="Mark Delivered to Customer" type="object" class="btn-success" invisible="state != 'dispatched'"/>
<field name="state" widget="statusbar" statusbar_visible="draft,packed,dispatched,delivered"/>
</header>
<sheet>
<div class="oe_title">
<label for="name" string="Dispatch Order Number"/>
<h1><field name="name"/></h1>
</div>
<group>
<group string="Buyer &amp; Sales Order">
<field name="customer_id"/>
<field name="sale_order_id"/>
<field name="production_plan_id"/>
<field name="date_dispatch"/>
</group>
<group string="Carrier &amp; Shipping Details">
<field name="carrier_name"/>
<field name="bl_awb_no"/>
<field name="container_no"/>
<field name="seal_no"/>
<field name="vehicle_no"/>
<field name="port_of_loading"/>
<field name="port_of_discharge"/>
</group>
</group>
<group string="Shipment Cargo Metrics">
<group>
<field name="total_cartons" class="fw-bold"/>
<field name="total_pieces" class="fw-bold fs-4 text-success"/>
</group>
<group>
<field name="total_gross_weight"/>
<field name="total_cbm"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<notebook>
<page string="Loaded Cartons" name="cartons_tab">
<field name="carton_ids">
<list>
<field name="carton_no"/>
<field name="name"/>
<field name="barcode"/>
<field name="total_pieces" sum="Total Pieces"/>
<field name="gross_weight_kg"/>
<field name="cbm"/>
</list>
</field>
</page>
<page string="Shipping Marks &amp; Instructions" name="notes_tab">
<field name="notes" placeholder="Enter side marks, port marks, consignee notify party details..."/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- Action & Menu -->
<record id="action_garment_dispatch" model="ir.actions.act_window">
<field name="name">Dispatch &amp; Delivery Orders</field>
<field name="res_model">garment.dispatch</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_garment_dispatch" name="Dispatch &amp; Shipments" parent="menu_garment_packing_root" action="action_garment_dispatch" sequence="30"/>
</odoo>

View File

@ -0,0 +1,106 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Fabric Inspection List View -->
<record id="view_garment_fabric_inspection_list" model="ir.ui.view">
<field name="name">garment.fabric.inspection.list</field>
<field name="model">garment.fabric.inspection</field>
<field name="arch" type="xml">
<list string="Fabric 4-Point Inspections" sample="1"
decoration-success="state == 'passed'"
decoration-danger="state == 'failed'">
<field name="name"/>
<field name="roll_id"/>
<field name="fabric_product_id"/>
<field name="shade_lot"/>
<field name="shade_grade"/>
<field name="score_per_100_sqm"/>
<field name="max_allowed_score"/>
<field name="passed_meters"/>
<field name="inspector_id"/>
<field name="date_inspected"/>
<field name="state" widget="badge"
decoration-success="state == 'passed'"
decoration-danger="state == 'failed'"/>
</list>
</field>
</record>
<!-- Fabric Inspection Form View -->
<record id="view_garment_fabric_inspection_form" model="ir.ui.view">
<field name="name">garment.fabric.inspection.form</field>
<field name="model">garment.fabric.inspection</field>
<field name="arch" type="xml">
<form string="Fabric 4-Point Inspection">
<header>
<button name="action_pass" string="Approve Roll (Quality Pass)" type="object" class="btn-success" invisible="state == 'passed'"/>
<button name="action_fail" string="Reject Roll (Quarantine)" type="object" class="btn-danger" invisible="state == 'failed'"/>
<field name="state" widget="statusbar" statusbar_visible="draft,inspected,passed"/>
</header>
<sheet>
<div class="oe_title">
<label for="name" string="Inspection Report Number"/>
<h1><field name="name"/></h1>
</div>
<group>
<group string="Roll &amp; Origin">
<field name="roll_id"/>
<field name="fabric_product_id"/>
<field name="supplier_id"/>
<field name="shade_lot"/>
</group>
<group string="Inspector &amp; Audit Date">
<field name="inspector_id"/>
<field name="date_inspected"/>
<field name="inspected_meters"/>
</group>
</group>
<group string="Physical Verification &amp; Shade Grading">
<group>
<label for="actual_gsm" string="GSM (Actual / Target)"/>
<div class="o_row">
<field name="actual_gsm"/> / <field name="target_gsm"/> GSM
</div>
<label for="actual_width" string="Width (Actual / Target)"/>
<div class="o_row">
<field name="actual_width"/> / <field name="target_width"/> Inches
</div>
</group>
<group>
<field name="shade_grade" widget="radio"/>
</group>
</group>
<group string="4-Point Defect Scoring System">
<group>
<field name="defects_1pt_count" string="1-Point Defects (&lt;= 3 inches)"/>
<field name="defects_2pt_count" string="2-Point Defects (3 - 6 inches)"/>
<field name="defects_3pt_count" string="3-Point Defects (6 - 9 inches)"/>
<field name="defects_4pt_count" string="4-Point Defects (&gt; 9 inches / Any Hole)"/>
</group>
<group>
<field name="total_defect_points" class="fw-bold"/>
<field name="score_per_100_sqm" class="fw-bold fs-4 text-primary"/>
<field name="max_allowed_score"/>
</group>
</group>
<notebook>
<page string="Inspector Observations &amp; Verdict" name="remarks_tab">
<field name="remarks" placeholder="Document any slub patterns, needle lines, bowing/skewing percentage, or shade variance notes..."/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- Action & Menu -->
<record id="action_garment_fabric_inspection" model="ir.actions.act_window">
<field name="name">Fabric 4-Point Inspections</field>
<field name="res_model">garment.fabric.inspection</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_garment_fabric_inspection" name="Fabric 4-Point Inspections" parent="menu_garment_inventory_root" action="action_garment_fabric_inspection" sequence="20"/>
</odoo>

View File

@ -0,0 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Fabric Roll List View -->
<record id="view_garment_fabric_roll_list" model="ir.ui.view">
<field name="name">garment.fabric.roll.list</field>
<field name="model">garment.fabric.roll</field>
<field name="arch" type="xml">
<list string="Fabric Rolls" sample="1"
decoration-success="status == 'approved'"
decoration-danger="status == 'rejected'"
decoration-info="status == 'received'"
decoration-warning="status == 'issued'"
decoration-muted="status == 'exhausted'">
<field name="name"/>
<field name="product_id"/>
<field name="color_id"/>
<field name="shade_lot"/>
<field name="gsm"/>
<field name="width"/>
<field name="net_weight" sum="Total Net Kg"/>
<field name="issued_weight" sum="Total Issued Kg"/>
<field name="remaining_weight" sum="Total Remaining Kg" class="fw-bold"/>
<field name="supplier_id"/>
<field name="date_received"/>
<field name="status" widget="badge"
decoration-success="status == 'approved'"
decoration-danger="status == 'rejected'"
decoration-info="status == 'received'"
decoration-warning="status == 'issued'"/>
</list>
</field>
</record>
<!-- Fabric Roll Form View -->
<record id="view_garment_fabric_roll_form" model="ir.ui.view">
<field name="name">garment.fabric.roll.form</field>
<field name="model">garment.fabric.roll</field>
<field name="arch" type="xml">
<form string="Fabric Roll">
<header>
<button name="action_create_inspection" string="Conduct 4-Point Inspection" type="object" class="btn-primary" invisible="status not in ['received', 'inspected']"/>
<field name="status" widget="statusbar" statusbar_visible="received,inspected,approved,issued,exhausted"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_create_inspection" type="object" class="oe_stat_button" icon="fa-check-square-o">
<field name="inspection_count" widget="statinfo" string="Inspections"/>
</button>
</div>
<div class="oe_title">
<label for="name" string="Roll Number"/>
<h1><field name="name"/></h1>
</div>
<group>
<group string="Fabric Specification">
<field name="product_id"/>
<field name="fabric_type_id"/>
<field name="color_id"/>
<field name="shade_lot"/>
<field name="barcode"/>
</group>
<group string="Physical Dimensions">
<field name="gsm"/>
<label for="width"/>
<div class="o_row">
<field name="width"/> Inches (<field name="width_type"/>)
</div>
<field name="length_meters"/>
<field name="yarn_count"/>
</group>
</group>
<group string="Weight &amp; Balance (Kg)">
<group>
<field name="gross_weight"/>
<field name="tare_weight"/>
<field name="net_weight" class="fw-bold"/>
</group>
<group>
<field name="issued_weight"/>
<field name="scrap_weight"/>
<field name="remaining_weight" class="fw-bold fs-4 text-success"/>
</group>
</group>
<group string="Receipt &amp; Storage">
<group>
<field name="supplier_id"/>
<field name="purchase_order_id"/>
</group>
<group>
<field name="date_received"/>
<field name="location_id"/>
</group>
</group>
<notebook>
<page string="4-Point Inspections" name="inspections_tab">
<field name="inspection_ids" readonly="1">
<list>
<field name="name"/>
<field name="date_inspected"/>
<field name="inspector_id"/>
<field name="score_per_100_sqm"/>
<field name="shade_grade"/>
<field name="state" widget="badge" decoration-success="state == 'passed'" decoration-danger="state == 'failed'"/>
</list>
</field>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- Search View -->
<record id="view_garment_fabric_roll_search" model="ir.ui.view">
<field name="name">garment.fabric.roll.search</field>
<field name="model">garment.fabric.roll</field>
<field name="arch" type="xml">
<search string="Search Fabric Rolls">
<field name="name"/>
<field name="shade_lot"/>
<field name="product_id"/>
<field name="supplier_id"/>
<separator/>
<filter string="Ready to Cut (Approved)" name="approved" domain="[('status', '=', 'approved')]"/>
<filter string="Uninspected" name="received" domain="[('status', '=', 'received')]"/>
<filter string="Rejected (Quarantine)" name="rejected" domain="[('status', '=', 'rejected')]"/>
<filter string="In Stock (>0 Kg)" name="in_stock" domain="[('remaining_weight', '>', 0)]"/>
<group>
<filter string="Fabric Product" name="group_product" context="{'group_by': 'product_id'}"/>
<filter string="Shade / Dyeing Lot" name="group_lot" context="{'group_by': 'shade_lot'}"/>
<filter string="Supplier" name="group_supplier" context="{'group_by': 'supplier_id'}"/>
<filter string="Status" name="group_status" context="{'group_by': 'status'}"/>
</group>
</search>
</field>
</record>
<!-- Action & Menu -->
<record id="action_garment_fabric_roll" model="ir.actions.act_window">
<field name="name">Fabric Rolls Inventory</field>
<field name="res_model">garment.fabric.roll</field>
<field name="view_mode">list,form</field>
<field name="context">{'search_default_in_stock': 1}</field>
</record>
<menuitem id="menu_garment_fabric_roll" name="Fabric Rolls" parent="menu_garment_inventory_root" action="action_garment_fabric_roll" sequence="10"/>
</odoo>

View File

@ -0,0 +1,84 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Finished Goods List View -->
<record id="view_garment_finished_goods_list" model="ir.ui.view">
<field name="name">garment.finished.goods.list</field>
<field name="model">garment.finished.goods</field>
<field name="arch" type="xml">
<list string="Finished Goods Stock" sample="1">
<field name="sku"/>
<field name="style_id"/>
<field name="color_id"/>
<field name="size_id"/>
<field name="on_hand_qty" sum="Total On Hand"/>
<field name="reserved_qty" sum="Total Reserved"/>
<field name="available_qty" sum="Total Available" class="fw-bold text-success"/>
<field name="shipped_qty" sum="Total Shipped"/>
<field name="location_id"/>
</list>
</field>
</record>
<!-- Finished Goods Form View -->
<record id="view_garment_finished_goods_form" model="ir.ui.view">
<field name="name">garment.finished.goods.form</field>
<field name="model">garment.finished.goods</field>
<field name="arch" type="xml">
<form string="Finished Goods Variant Stock">
<sheet>
<div class="oe_title">
<label for="sku" string="Variant SKU"/>
<h1><field name="sku"/></h1>
</div>
<group>
<group string="Product Variant">
<field name="style_id"/>
<field name="color_id"/>
<field name="size_id"/>
<field name="product_id"/>
</group>
<group string="Inventory Balances (Pieces)">
<field name="on_hand_qty" class="fw-bold"/>
<field name="reserved_qty"/>
<field name="available_qty" class="fw-bold fs-4 text-success"/>
<field name="shipped_qty" class="fw-bold text-primary"/>
<field name="location_id"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<!-- Search View -->
<record id="view_garment_finished_goods_search" model="ir.ui.view">
<field name="name">garment.finished.goods.search</field>
<field name="model">garment.finished.goods</field>
<field name="arch" type="xml">
<search string="Search Finished Goods">
<field name="sku"/>
<field name="style_id"/>
<field name="color_id"/>
<field name="size_id"/>
<filter string="In Stock (>0)" name="in_stock" domain="[('on_hand_qty', '>', 0)]"/>
<group>
<filter string="Garment Style" name="group_style" context="{'group_by': 'style_id'}"/>
<filter string="Color" name="group_color" context="{'group_by': 'color_id'}"/>
<filter string="Size" name="group_size" context="{'group_by': 'size_id'}"/>
</group>
</search>
</field>
</record>
<!-- Action & Menu -->
<record id="action_garment_finished_goods" model="ir.actions.act_window">
<field name="name">Finished Goods Inventory</field>
<field name="res_model">garment.finished.goods</field>
<field name="view_mode">list,form</field>
<field name="context">{'search_default_in_stock': 1}</field>
</record>
<menuitem id="menu_garment_finished_goods" name="Finished Goods Inventory" parent="menu_garment_inventory_root" action="action_garment_finished_goods" sequence="30"/>
</odoo>

View File

@ -0,0 +1,228 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Color Views -->
<record id="view_garment_color_list" model="ir.ui.view">
<field name="name">garment.color.list</field>
<field name="model">garment.color</field>
<field name="arch" type="xml">
<list string="Garment Colors">
<field name="code"/>
<field name="name"/>
<field name="color_group"/>
<field name="pantone_code"/>
<field name="color_hex" widget="color"/>
<field name="active"/>
</list>
</field>
</record>
<record id="view_garment_color_form" model="ir.ui.view">
<field name="name">garment.color.form</field>
<field name="model">garment.color</field>
<field name="arch" type="xml">
<form string="Garment Color">
<sheet>
<group>
<group>
<field name="name"/>
<field name="code"/>
<field name="color_group"/>
</group>
<group>
<field name="pantone_code"/>
<field name="color_hex" widget="color"/>
<field name="active"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_garment_color" model="ir.actions.act_window">
<field name="name">Colors Master</field>
<field name="res_model">garment.color</field>
<field name="view_mode">list,form</field>
</record>
<!-- Size Views -->
<record id="view_garment_size_list" model="ir.ui.view">
<field name="name">garment.size.list</field>
<field name="model">garment.size</field>
<field name="arch" type="xml">
<list string="Garment Sizes">
<field name="sequence" widget="handle"/>
<field name="code"/>
<field name="name"/>
<field name="size_group"/>
<field name="active"/>
</list>
</field>
</record>
<record id="view_garment_size_form" model="ir.ui.view">
<field name="name">garment.size.form</field>
<field name="model">garment.size</field>
<field name="arch" type="xml">
<form string="Garment Size">
<sheet>
<group>
<group>
<field name="code"/>
<field name="name"/>
<field name="sequence"/>
</group>
<group>
<field name="size_group"/>
<field name="active"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_garment_size" model="ir.actions.act_window">
<field name="name">Sizes Master</field>
<field name="res_model">garment.size</field>
<field name="view_mode">list,form</field>
</record>
<!-- Fabric Types Views -->
<record id="view_garment_fabric_type_list" model="ir.ui.view">
<field name="name">garment.fabric.type.list</field>
<field name="model">garment.fabric.type</field>
<field name="arch" type="xml">
<list string="Fabric Types &amp; Structures">
<field name="code"/>
<field name="name"/>
<field name="structure"/>
<field name="gsm_min"/>
<field name="gsm_max"/>
</list>
</field>
</record>
<record id="view_garment_fabric_type_form" model="ir.ui.view">
<field name="name">garment.fabric.type.form</field>
<field name="model">garment.fabric.type</field>
<field name="arch" type="xml">
<form string="Fabric Type">
<sheet>
<group>
<group>
<field name="name"/>
<field name="code"/>
<field name="structure"/>
</group>
<group>
<field name="gsm_min"/>
<field name="gsm_max"/>
</group>
</group>
<notebook>
<page string="Description">
<field name="description"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="action_garment_fabric_type" model="ir.actions.act_window">
<field name="name">Fabric Knit Structures</field>
<field name="res_model">garment.fabric.type</field>
<field name="view_mode">list,form</field>
</record>
<!-- Composition Views -->
<record id="view_garment_composition_list" model="ir.ui.view">
<field name="name">garment.composition.list</field>
<field name="model">garment.composition</field>
<field name="arch" type="xml">
<list string="Compositions">
<field name="code"/>
<field name="name"/>
<field name="cotton_pct"/>
<field name="polyester_pct"/>
<field name="spandex_pct"/>
</list>
</field>
</record>
<record id="action_garment_composition" model="ir.actions.act_window">
<field name="name">Fabric Compositions</field>
<field name="res_model">garment.composition</field>
<field name="view_mode">list,form</field>
</record>
<!-- Brand Views -->
<record id="view_garment_brand_list" model="ir.ui.view">
<field name="name">garment.brand.list</field>
<field name="model">garment.brand</field>
<field name="arch" type="xml">
<list string="Brands">
<field name="code"/>
<field name="name"/>
<field name="active"/>
</list>
</field>
</record>
<record id="action_garment_brand" model="ir.actions.act_window">
<field name="name">Brands Master</field>
<field name="res_model">garment.brand</field>
<field name="view_mode">list,form</field>
</record>
<!-- Season Views -->
<record id="view_garment_season_list" model="ir.ui.view">
<field name="name">garment.season.list</field>
<field name="model">garment.season</field>
<field name="arch" type="xml">
<list string="Seasons">
<field name="code"/>
<field name="name"/>
<field name="year"/>
<field name="active"/>
</list>
</field>
</record>
<record id="action_garment_season" model="ir.actions.act_window">
<field name="name">Seasons Master</field>
<field name="res_model">garment.season</field>
<field name="view_mode">list,form</field>
</record>
<!-- Garment Type Views -->
<record id="view_garment_type_list" model="ir.ui.view">
<field name="name">garment.type.list</field>
<field name="model">garment.type</field>
<field name="arch" type="xml">
<list string="Garment Types">
<field name="code"/>
<field name="name"/>
<field name="category"/>
</list>
</field>
</record>
<record id="action_garment_type" model="ir.actions.act_window">
<field name="name">Garment Categories</field>
<field name="res_model">garment.type</field>
<field name="view_mode">list,form</field>
</record>
<!-- Menu Items under Configuration -->
<menuitem id="menu_garment_color" name="Colors" parent="menu_garment_config_root" action="action_garment_color" sequence="10"/>
<menuitem id="menu_garment_size" name="Sizes" parent="menu_garment_config_root" action="action_garment_size" sequence="20"/>
<menuitem id="menu_garment_fabric_type" name="Fabric Knit Structures" parent="menu_garment_config_root" action="action_garment_fabric_type" sequence="30"/>
<menuitem id="menu_garment_composition" name="Compositions" parent="menu_garment_config_root" action="action_garment_composition" sequence="40"/>
<menuitem id="menu_garment_brand" name="Brands" parent="menu_garment_config_root" action="action_garment_brand" sequence="50"/>
<menuitem id="menu_garment_season" name="Seasons" parent="menu_garment_config_root" action="action_garment_season" sequence="60"/>
<menuitem id="menu_garment_type" name="Garment Types" parent="menu_garment_config_root" action="action_garment_type" sequence="70"/>
</odoo>

View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- BOM List View -->
<record id="view_garment_bom_list" model="ir.ui.view">
<field name="name">garment.bom.list</field>
<field name="model">garment.bom</field>
<field name="arch" type="xml">
<list string="Garment BOMs" sample="1">
<field name="name"/>
<field name="style_id"/>
<field name="base_qty"/>
<field name="total_fabric_cost" widget="monetary"/>
<field name="total_trim_cost" widget="monetary"/>
<field name="total_operation_cost" widget="monetary"/>
<field name="total_unit_cost" widget="monetary" class="fw-bold text-success"/>
<field name="active"/>
</list>
</field>
</record>
<!-- BOM Form View -->
<record id="view_garment_bom_form" model="ir.ui.view">
<field name="name">garment.bom.form</field>
<field name="model">garment.bom</field>
<field name="arch" type="xml">
<form string="Garment Bill of Materials">
<sheet>
<div class="oe_title">
<label for="name" string="BOM Reference"/>
<h1><field name="name"/></h1>
</div>
<group>
<group string="Style &amp; Quantity">
<field name="style_id"/>
<label for="base_qty"/>
<div class="o_row">
<field name="base_qty"/>
<field name="uom_id"/>
</div>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group string="Cost Summary (per piece)">
<field name="total_fabric_cost" widget="monetary"/>
<field name="total_trim_cost" widget="monetary"/>
<field name="total_operation_cost" widget="monetary"/>
<field name="total_unit_cost" widget="monetary" class="fw-bold fs-4 text-primary"/>
</group>
</group>
<notebook>
<page string="1. Fabric Components" name="fabric_page">
<field name="fabric_line_ids">
<list editable="bottom">
<field name="component_type"/>
<field name="fabric_type_id"/>
<field name="product_id"/>
<field name="gsm"/>
<field name="width"/>
<field name="consumption_kg"/>
<field name="wastage_pct"/>
<field name="gross_consumption_kg"/>
<field name="cost_per_kg"/>
<field name="subtotal_cost" sum="Total Fabric Cost"/>
</list>
</field>
</page>
<page string="2. Trims &amp; Accessories" name="trims_page">
<field name="trim_line_ids">
<list editable="bottom">
<field name="trim_type"/>
<field name="item_name"/>
<field name="product_id"/>
<field name="consumption_qty"/>
<field name="wastage_pct"/>
<field name="gross_qty"/>
<field name="uom_id"/>
<field name="unit_cost"/>
<field name="subtotal_cost" sum="Total Trims Cost"/>
</list>
</field>
</page>
<page string="3. Operations &amp; Routing (CM)" name="operations_page">
<field name="operation_line_ids">
<list editable="bottom">
<field name="sequence" widget="handle"/>
<field name="operation_name"/>
<field name="department"/>
<field name="machine_type"/>
<field name="sam" sum="Total SAM"/>
<field name="minute_rate"/>
<field name="subtotal_cost" sum="Total CM Cost"/>
</list>
</field>
</page>
<page string="Technical Notes" name="notes_page">
<field name="notes" placeholder="Fabric shrinkage allowance, thread type recommendations, stitching parameters..."/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- Action & Menu -->
<record id="action_garment_bom" model="ir.actions.act_window">
<field name="name">Garment BOMs</field>
<field name="res_model">garment.bom</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_garment_bom" name="Garment BOMs" parent="menu_garment_planning_root" action="action_garment_bom" sequence="10"/>
</odoo>

View File

@ -0,0 +1,132 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Costing List View -->
<record id="view_garment_costing_list" model="ir.ui.view">
<field name="name">garment.costing.list</field>
<field name="model">garment.costing</field>
<field name="arch" type="xml">
<list string="Garment Cost Sheets" sample="1">
<field name="name"/>
<field name="style_id"/>
<field name="buyer_id"/>
<field name="order_qty" sum="Total Pcs"/>
<field name="costing_type"/>
<field name="total_cost_per_pc" widget="monetary"/>
<field name="target_selling_price" widget="monetary"/>
<field name="gross_margin_amount" widget="monetary"/>
<field name="gross_margin_pct" widget="percentage" class="fw-bold text-success"/>
<field name="total_expected_profit" widget="monetary" sum="Total Profit"/>
</list>
</field>
</record>
<!-- Costing Form View -->
<record id="view_garment_costing_form" model="ir.ui.view">
<field name="name">garment.costing.form</field>
<field name="model">garment.costing</field>
<field name="arch" type="xml">
<form string="Garment Cost Sheet">
<header>
<button name="action_load_from_bom" string="Load Costs from Active BOM" type="object" class="btn-secondary" icon="fa-download"/>
</header>
<sheet>
<div class="oe_title">
<label for="name" string="Cost Sheet Reference"/>
<h1><field name="name"/></h1>
</div>
<group>
<group string="Commercial Reference">
<field name="style_id"/>
<field name="buyer_id"/>
<field name="order_qty"/>
<field name="costing_type" widget="radio"/>
</group>
<group string="Currency &amp; Company">
<field name="currency_id"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<!-- Profitability KPI Highlight Banner -->
<div class="row my-3 p-3 bg-light rounded text-center">
<div class="col-3">
<span class="text-muted small text-uppercase">Total Cost / Pc</span>
<div class="fs-4 fw-bold text-dark"><field name="total_cost_per_pc" widget="monetary"/></div>
<span class="small text-muted">(<field name="total_cost_per_dozen" widget="monetary"/> / Dozen)</span>
</div>
<div class="col-3">
<span class="text-muted small text-uppercase">Selling Price / Pc</span>
<div class="fs-4 fw-bold text-primary"><field name="target_selling_price" widget="monetary"/></div>
</div>
<div class="col-3">
<span class="text-muted small text-uppercase">Gross Margin %</span>
<div class="fs-4 fw-bold text-success"><field name="gross_margin_pct"/>%</div>
<span class="small text-muted">(<field name="gross_margin_amount" widget="monetary"/> / Pc)</span>
</div>
<div class="col-3">
<span class="text-muted small text-uppercase">Total Expected Profit</span>
<div class="fs-4 fw-bold text-success"><field name="total_expected_profit" widget="monetary"/></div>
</div>
</div>
<group string="1. Raw Materials &amp; Processing (Per Piece)">
<group>
<field name="fabric_cost_per_pc" widget="monetary"/>
</group>
<group>
<field name="dyeing_finishing_cost_per_pc" widget="monetary"/>
</group>
</group>
<group string="2. Trims &amp; Value Addition (Per Piece)">
<group>
<field name="trims_accessories_cost_per_pc" widget="monetary"/>
<field name="printing_embroidery_cost_per_pc" widget="monetary"/>
</group>
<group>
<field name="washing_cost_per_pc" widget="monetary"/>
</group>
</group>
<group string="3. Manufacturing Labor &amp; Packaging (Per Piece)">
<group>
<field name="cutting_cost_per_pc" widget="monetary"/>
<field name="sewing_cm_cost_per_pc" widget="monetary"/>
</group>
<group>
<field name="packing_carton_cost_per_pc" widget="monetary"/>
<field name="commercial_freight_cost_per_pc" widget="monetary"/>
</group>
</group>
<group string="4. Overheads &amp; Administration">
<group>
<field name="factory_overhead_pct"/>
</group>
<group>
<field name="factory_overhead_cost" widget="monetary"/>
</group>
</group>
<notebook>
<page string="Costing Notes &amp; Assumptions" name="notes_tab">
<field name="notes" placeholder="Specify exchange rate, buyer commission, payment terms (L/C 60 days, TT), minimum order quantity..."/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- Action & Menu -->
<record id="action_garment_costing" model="ir.actions.act_window">
<field name="name">Garment Costing Sheets</field>
<field name="res_model">garment.costing</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_garment_costing" name="Garment Pre &amp; Post Costing" parent="menu_garment_merchandising_root" action="action_garment_costing" sequence="30"/>
</odoo>

View File

@ -0,0 +1,163 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Dashboard Form View -->
<record id="view_garment_dashboard_form" model="ir.ui.view">
<field name="name">garment.dashboard.form</field>
<field name="model">garment.dashboard</field>
<field name="arch" type="xml">
<form string="Tiruppur Factory Control Room" create="false" edit="false" delete="false">
<sheet>
<div class="oe_title">
<h1 class="text-primary mb-3">Tiruppur Garment Manufacturing - Live Control Center</h1>
</div>
<!-- Row 1: Merchandising & Production Header Cards -->
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="p-3 bg-light border rounded shadow-sm">
<div class="text-muted text-uppercase small fw-bold">Active Orders</div>
<div class="fs-3 fw-bold text-dark"><field name="total_sales_orders"/> Orders</div>
<div class="small text-muted"><field name="total_ordered_pieces"/> Total Pieces</div>
</div>
</div>
<div class="col-md-3">
<div class="p-3 bg-light border rounded shadow-sm">
<div class="text-muted text-uppercase small fw-bold">Production in Progress</div>
<div class="fs-3 fw-bold text-primary"><field name="active_production_plans"/> Plans</div>
<div class="small text-success">Avg Progress: <field name="avg_production_progress"/>%</div>
</div>
</div>
<div class="col-md-3">
<div class="p-3 bg-light border rounded shadow-sm">
<div class="text-muted text-uppercase small fw-bold">Today's Sewing Output</div>
<div class="fs-3 fw-bold text-success"><field name="today_actual_pieces"/> Pcs</div>
<div class="small text-muted">Target: <field name="today_target_pieces"/> Pcs</div>
</div>
</div>
<div class="col-md-3">
<div class="p-3 bg-light border rounded shadow-sm">
<div class="text-muted text-uppercase small fw-bold">Sewing Floor Efficiency</div>
<div class="fs-3 fw-bold text-info"><field name="avg_line_efficiency"/>%</div>
<div class="small text-danger"><field name="today_defects"/> Alterations Logged</div>
</div>
</div>
</div>
<!-- Row 2: Inventory, Subcontracting & Quality Cards -->
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="p-3 bg-light border rounded shadow-sm">
<div class="text-muted text-uppercase small fw-bold">Fabric Roll Stock</div>
<div class="fs-3 fw-bold text-dark"><field name="total_fabric_stock_kg"/> Kg</div>
<div class="small text-success"><field name="total_approved_rolls"/> QC Approved Rolls</div>
</div>
</div>
<div class="col-md-3">
<div class="p-3 bg-light border rounded shadow-sm">
<div class="text-muted text-uppercase small fw-bold">Finished Goods Stock</div>
<div class="fs-3 fw-bold text-success"><field name="total_finished_goods_pcs"/> Pcs</div>
<div class="small text-muted">Ready in Warehouse</div>
</div>
</div>
<div class="col-md-3">
<div class="p-3 bg-light border rounded shadow-sm">
<div class="text-muted text-uppercase small fw-bold">Subcontracting (Job Work)</div>
<div class="fs-3 fw-bold text-warning"><field name="pending_jobwork_dc"/> DCs</div>
<div class="small text-muted">Outward in Processing</div>
</div>
</div>
<div class="col-md-3">
<div class="p-3 bg-light border rounded shadow-sm">
<div class="text-muted text-uppercase small fw-bold">Ready for Dispatch</div>
<div class="fs-3 fw-bold text-primary"><field name="pending_dispatch_cartons"/> Cartons</div>
<div class="small text-danger"><field name="active_rework_orders"/> Pending Rework</div>
</div>
</div>
</div>
<!-- Department Quick Launchpad -->
<separator string="Department Navigation &amp; Quick Actions"/>
<div class="row g-2 mb-3">
<div class="col-md-2">
<a class="btn btn-outline-primary w-100 py-2" role="button" href="/odoo/action-garment_erp.action_garment_style">
<i class="fa fa-tag me-1"/> Garment Styles
</a>
</div>
<div class="col-md-2">
<a class="btn btn-outline-primary w-100 py-2" role="button" href="/odoo/action-garment_erp.action_garment_sales_order">
<i class="fa fa-shopping-cart me-1"/> Sales Matrix
</a>
</div>
<div class="col-md-2">
<a class="btn btn-outline-primary w-100 py-2" role="button" href="/odoo/action-garment_erp.action_garment_production_plan">
<i class="fa fa-industry me-1"/> Production Plans
</a>
</div>
<div class="col-md-2">
<a class="btn btn-outline-primary w-100 py-2" role="button" href="/odoo/action-garment_erp.action_garment_cutting_plan">
<i class="fa fa-scissors me-1"/> Cutting Orders
</a>
</div>
<div class="col-md-2">
<a class="btn btn-outline-primary w-100 py-2" role="button" href="/odoo/action-garment_erp.action_garment_bundle">
<i class="fa fa-tags me-1"/> Bundles Tracker
</a>
</div>
<div class="col-md-2">
<a class="btn btn-outline-primary w-100 py-2" role="button" href="/odoo/action-garment_erp.action_garment_sewing_hourly">
<i class="fa fa-clock-o me-1"/> Hourly Production
</a>
</div>
</div>
<div class="row g-2">
<div class="col-md-2">
<a class="btn btn-outline-secondary w-100 py-2" role="button" href="/odoo/action-garment_erp.action_garment_fabric_roll">
<i class="fa fa-cubes me-1"/> Fabric Rolls
</a>
</div>
<div class="col-md-2">
<a class="btn btn-outline-secondary w-100 py-2" role="button" href="/odoo/action-garment_erp.action_garment_jobwork_order">
<i class="fa fa-truck me-1"/> Job Work DCs
</a>
</div>
<div class="col-md-2">
<a class="btn btn-outline-secondary w-100 py-2" role="button" href="/odoo/action-garment_erp.action_garment_quality_inspection">
<i class="fa fa-check-circle me-1"/> Quality QC
</a>
</div>
<div class="col-md-2">
<a class="btn btn-outline-secondary w-100 py-2" role="button" href="/odoo/action-garment_erp.action_garment_packing_plan">
<i class="fa fa-archive me-1"/> Packing Plans
</a>
</div>
<div class="col-md-2">
<a class="btn btn-outline-secondary w-100 py-2" role="button" href="/odoo/action-garment_erp.action_garment_dispatch">
<i class="fa fa-ship me-1"/> Dispatch
</a>
</div>
<div class="col-md-2">
<a class="btn btn-outline-secondary w-100 py-2" role="button" href="/odoo/action-garment_erp.action_garment_costing">
<i class="fa fa-dollar me-1"/> Costing Sheets
</a>
</div>
</div>
</sheet>
</form>
</field>
</record>
<!-- Window Action -->
<record id="action_garment_dashboard" model="ir.actions.act_window">
<field name="name">Factory Control Room Dashboard</field>
<field name="res_model">garment.dashboard</field>
<field name="view_mode">form</field>
<field name="target">current</field>
<field name="res_id" eval="False"/>
</record>
<menuitem id="menu_garment_dashboard" name="Factory Live Dashboard" parent="menu_garment_dashboard_root" action="action_garment_dashboard" sequence="1"/>
</odoo>

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Top-level Garment ERP Root Menu -->
<menuitem id="menu_garment_root"
name="Garment ERP"
web_icon="garment_erp,static/description/icon.png"
sequence="10"
groups="group_garment_user"/>
<!-- Main Navigation Submenus -->
<menuitem id="menu_garment_dashboard_root"
name="Dashboards"
parent="menu_garment_root"
sequence="5"/>
<menuitem id="menu_garment_merchandising_root"
name="Merchandising"
parent="menu_garment_root"
sequence="10"/>
<menuitem id="menu_garment_planning_root"
name="Planning &amp; MRP"
parent="menu_garment_root"
sequence="20"/>
<menuitem id="menu_garment_cutting_root"
name="Cutting &amp; Bundling"
parent="menu_garment_root"
sequence="30"/>
<menuitem id="menu_garment_sewing_root"
name="Sewing Floor"
parent="menu_garment_root"
sequence="40"/>
<menuitem id="menu_garment_jobwork_root"
name="Subcontracting / Job Work"
parent="menu_garment_root"
sequence="50"/>
<menuitem id="menu_garment_quality_root"
name="Quality &amp; Rework"
parent="menu_garment_root"
sequence="60"/>
<menuitem id="menu_garment_inventory_root"
name="Fabric &amp; Stores"
parent="menu_garment_root"
sequence="70"/>
<menuitem id="menu_garment_packing_root"
name="Packing &amp; Dispatch"
parent="menu_garment_root"
sequence="80"/>
<menuitem id="menu_garment_config_root"
name="Configuration"
parent="menu_garment_root"
sequence="90"
groups="group_garment_admin"/>
</odoo>

View File

@ -0,0 +1,178 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Style Kanban View -->
<record id="view_garment_style_kanban" model="ir.ui.view">
<field name="name">garment.style.kanban</field>
<field name="model">garment.style</field>
<field name="arch" type="xml">
<kanban sample="1">
<field name="id"/>
<field name="name"/>
<field name="style_name"/>
<field name="brand_id"/>
<field name="gsm"/>
<field name="variant_count"/>
<field name="image_front"/>
<templates>
<t t-name="card">
<div class="d-flex flex-row p-2">
<div class="me-3" style="width: 70px; height: 85px; overflow: hidden; border-radius: 4px; background: #f8f9fa;">
<field name="image_front" widget="image" options="{'img_class': 'w-100 h-100 object-fit-cover'}" alt="Front"/>
</div>
<div class="flex-grow-1">
<div class="fw-bold fs-6 text-primary"><field name="name"/></div>
<div class="text-truncate fw-semibold text-dark" style="max-width: 180px;"><field name="style_name"/></div>
<div class="text-muted small"><field name="brand_id"/> · <field name="gsm"/> GSM</div>
<div class="mt-2">
<span class="badge bg-secondary"><field name="variant_count"/> Variants</span>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- Style List View -->
<record id="view_garment_style_list" model="ir.ui.view">
<field name="name">garment.style.list</field>
<field name="model">garment.style</field>
<field name="arch" type="xml">
<list string="Garment Styles" sample="1">
<field name="name"/>
<field name="style_name"/>
<field name="brand_id"/>
<field name="season_id"/>
<field name="garment_type_id"/>
<field name="fabric_type_id"/>
<field name="gsm"/>
<field name="sam"/>
<field name="variant_count" string="Variants"/>
<field name="production_count" string="Production"/>
</list>
</field>
</record>
<!-- Style Form View -->
<record id="view_garment_style_form" model="ir.ui.view">
<field name="name">garment.style.form</field>
<field name="model">garment.style</field>
<field name="arch" type="xml">
<form string="Garment Style">
<header>
<button name="action_generate_product_variants" string="Sync Product Catalog Variants" type="object" class="btn-primary"/>
<button name="action_view_variants" string="View Variant SKUs" type="object" class="btn-secondary"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_view_variants" type="object" class="oe_stat_button" icon="fa-th-list">
<field name="variant_count" widget="statinfo" string="Variants"/>
</button>
<button name="action_view_boms" type="object" class="oe_stat_button" icon="fa-cubes">
<field name="bom_count" widget="statinfo" string="BOMs"/>
</button>
<button name="action_view_production" type="object" class="oe_stat_button" icon="fa-cogs">
<field name="production_count" widget="statinfo" string="Production"/>
</button>
</div>
<field name="image_front" widget="image" class="oe_avatar" options="{'size': [110, 110]}"/>
<div class="oe_title">
<label for="name" string="Style Code"/>
<h1><field name="name" placeholder="e.g. TSHIRT-001"/></h1>
<h3><field name="style_name" placeholder="Descriptive Name e.g. Men's Bio-Washed Crew Neck T-Shirt"/></h3>
</div>
<group>
<group string="Classification">
<field name="brand_id"/>
<field name="season_id"/>
<field name="gender_id"/>
<field name="garment_type_id"/>
<field name="buyer_id"/>
<field name="customer_style_ref"/>
</group>
<group string="Knitwear Fabric Specifications (Tiruppur)">
<field name="fabric_type_id"/>
<field name="composition_id"/>
<field name="gsm"/>
<label for="fabric_width"/>
<div class="o_row">
<field name="fabric_width"/>
<field name="width_type"/>
</div>
<field name="yarn_count"/>
<label for="shrinkage_length_pct" string="Shrinkage (L x W) %"/>
<div class="o_row">
<field name="shrinkage_length_pct"/>% ×
<field name="shrinkage_width_pct"/>%
</div>
</group>
</group>
<group>
<group string="Variant Matrix Configuration">
<field name="color_ids" widget="many2many_tags" options="{'color_field': 'color_hex'}"/>
<field name="size_ids" widget="many2many_tags"/>
<field name="uom_id"/>
</group>
<group string="Industrial Engineering (IE)">
<field name="sam" help="Sewing Standard Allowed Minutes"/>
<field name="smv" help="Total Standard Minute Value"/>
<field name="hsn_code"/>
</group>
</group>
<notebook>
<page string="Measurement Specs (POM)" name="measurement_page">
<field name="measurement_line_ids">
<list editable="bottom">
<field name="sequence" widget="handle"/>
<field name="pom_code"/>
<field name="pom_name"/>
<field name="tolerance_plus"/>
<field name="tolerance_minus"/>
<field name="val_xs"/>
<field name="val_s"/>
<field name="val_m"/>
<field name="val_l"/>
<field name="val_xl"/>
<field name="val_xxl"/>
<field name="val_3xl"/>
<field name="unit"/>
</list>
</field>
</page>
<page string="Sketches &amp; Tech Pack" name="media_page">
<group>
<group string="Sketches / Photos">
<field name="image_back" widget="image" options="{'size': [140, 140]}"/>
</group>
<group string="Tech Pack File">
<field name="techpack_attachment" filename="techpack_filename"/>
<field name="techpack_filename" invisible="1"/>
</group>
</group>
</page>
<page string="Construction Notes" name="notes_page">
<field name="notes" placeholder="Enter seam specs, stitch density (SPI), thread recommendations..."/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- Action & Menu -->
<record id="action_garment_style" model="ir.actions.act_window">
<field name="name">Garment Styles</field>
<field name="res_model">garment.style</field>
<field name="view_mode">kanban,list,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">Create your first Garment Style!</p>
<p>Define style code, fabric specifications (GSM, width, shrinkage), colors, and size measurements.</p>
</field>
</record>
<menuitem id="menu_garment_style" name="Garment Styles" parent="menu_garment_merchandising_root" action="action_garment_style" sequence="10"/>
</odoo>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Quick Barcode / QR Scan Traceability Action -->
<record id="action_garment_traceability_bundles" model="ir.actions.act_window">
<field name="name">Full Lifecycle Traceability Explorer</field>
<field name="res_model">garment.bundle</field>
<field name="view_mode">list,form</field>
<field name="help" type="html">
<p class="o_view_nocontent_smiling_face">Scan or Search any Barcode / QR Code</p>
<p>Search any Bundle, Fabric Roll, or Carton barcode to instantly trace from Buyer Sales Order down to Sewing Line and Operators.</p>
</field>
</record>
<menuitem id="menu_garment_traceability"
name="Traceability Explorer"
parent="menu_garment_root"
action="action_garment_traceability_bundles"
sequence="85"/>
</odoo>

View File

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Job Work List View -->
<record id="view_garment_jobwork_order_list" model="ir.ui.view">
<field name="name">garment.jobwork.order.list</field>
<field name="model">garment.jobwork.order</field>
<field name="arch" type="xml">
<list string="Job Work Delivery Challans" sample="1"
decoration-info="state == 'sent'"
decoration-warning="state == 'partial'"
decoration-success="state == 'completed'">
<field name="name"/>
<field name="vendor_id"/>
<field name="process_type"/>
<field name="production_plan_id"/>
<field name="style_id"/>
<field name="date_sent"/>
<field name="total_sent_qty" sum="Total Sent"/>
<field name="total_accepted_qty" sum="Total Inwarded"/>
<field name="pending_qty" sum="Total Pending" class="fw-bold text-danger"/>
<field name="actual_process_loss_pct" widget="percentage"/>
<field name="total_cost" widget="monetary" sum="Total Cost"/>
<field name="state" widget="badge"
decoration-info="state == 'sent'"
decoration-warning="state == 'partial'"
decoration-success="state == 'completed'"/>
</list>
</field>
</record>
<!-- Job Work Form View -->
<record id="view_garment_jobwork_order_form" model="ir.ui.view">
<field name="name">garment.jobwork.order.form</field>
<field name="model">garment.jobwork.order</field>
<field name="arch" type="xml">
<form string="Job Work Delivery Challan">
<header>
<button name="action_dispatch_material" string="Issue Outward DC" type="object" class="btn-primary" invisible="state != 'draft'"/>
<button name="action_complete_jobwork" string="Complete Job Work" type="object" class="btn-success" invisible="state not in ['sent', 'partial']"/>
<field name="state" widget="statusbar" statusbar_visible="draft,sent,partial,completed"/>
</header>
<sheet>
<div class="oe_title">
<label for="name" string="Job Work DC Reference"/>
<h1><field name="name"/></h1>
</div>
<group>
<group string="Subcontractor &amp; Process">
<field name="vendor_id"/>
<field name="process_type"/>
<field name="production_plan_id"/>
<field name="style_id"/>
</group>
<group string="Dates &amp; Transport Logistics">
<field name="date_sent"/>
<field name="date_expected"/>
<field name="vehicle_no"/>
<field name="e_way_bill_no"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<group string="Rates, Process Loss &amp; Billing">
<group>
<field name="pricing_uom"/>
<field name="rate_per_unit"/>
<field name="allowed_process_loss_pct" string="Standard Allowed Loss %"/>
</group>
<group>
<field name="actual_process_loss_pct" widget="percentage" class="fw-bold"/>
<field name="total_cost" widget="monetary" class="fw-bold fs-4 text-primary"/>
</group>
</group>
<div class="row my-3 p-3 bg-light rounded text-center">
<div class="col-3">
<span class="text-muted small text-uppercase">Total Sent Outward</span>
<div class="fs-4 fw-bold text-dark"><field name="total_sent_qty"/></div>
</div>
<div class="col-3">
<span class="text-muted small text-uppercase">Total Received Inward</span>
<div class="fs-4 fw-bold text-info"><field name="total_received_qty"/></div>
</div>
<div class="col-3">
<span class="text-muted small text-uppercase">Accepted Good Qty</span>
<div class="fs-4 fw-bold text-success"><field name="total_accepted_qty"/></div>
</div>
<div class="col-3">
<span class="text-muted small text-uppercase">Pending Balance</span>
<div class="fs-4 fw-bold text-danger"><field name="pending_qty"/></div>
</div>
</div>
<notebook>
<page string="1. Materials Sent Outward" name="sent_tab">
<field name="sent_line_ids">
<list editable="bottom">
<field name="description"/>
<field name="fabric_roll_id"/>
<field name="bundle_id"/>
<field name="color_id"/>
<field name="quantity" sum="Total Sent"/>
<field name="uom"/>
</list>
</field>
</page>
<page string="2. Materials Received Inward" name="inward_tab">
<field name="inward_line_ids">
<list editable="bottom">
<field name="date_inward"/>
<field name="vendor_dc_no"/>
<field name="received_qty" sum="Total Received"/>
<field name="rejected_qty" sum="Total Rejected"/>
<field name="accepted_qty" sum="Total Accepted"/>
<field name="remarks"/>
</list>
</field>
</page>
<page string="Job Work Instructions &amp; Shade Recipe" name="notes_tab">
<field name="notes" placeholder="Specify dye chemical formulations, print mesh count, pantone codes, embroidery stitch density..."/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- Action & Menu -->
<record id="action_garment_jobwork_order" model="ir.actions.act_window">
<field name="name">Job Work Orders (Subcontracting)</field>
<field name="res_model">garment.jobwork.order</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_garment_jobwork_order" name="Job Work Challans (DC)" parent="menu_garment_jobwork_root" action="action_garment_jobwork_order" sequence="10"/>
</odoo>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- MRP List View -->
<record id="view_garment_mrp_requirement_list" model="ir.ui.view">
<field name="name">garment.mrp.requirement.list</field>
<field name="model">garment.mrp.requirement</field>
<field name="arch" type="xml">
<list string="Material Requirements" sample="1">
<field name="name"/>
<field name="production_plan_id"/>
<field name="style_id"/>
<field name="order_qty"/>
<field name="total_shortage_cost" widget="monetary" sum="Total Shortage"/>
<field name="purchase_count" string="POs Created"/>
<field name="state" widget="badge"
decoration-info="state == 'calculated'"
decoration-success="state == 'rfq_created'"/>
</list>
</field>
</record>
<!-- MRP Form View -->
<record id="view_garment_mrp_requirement_form" model="ir.ui.view">
<field name="name">garment.mrp.requirement.form</field>
<field name="model">garment.mrp.requirement</field>
<field name="arch" type="xml">
<form string="Material Requirement Planning">
<header>
<button name="action_compute_requirements" string="Re-Calculate Needs" type="object" class="btn-primary" icon="fa-calculator"/>
<button name="action_generate_purchase_rfqs" string="Generate Purchase RFQs for Shortages" type="object" class="btn-success" icon="fa-shopping-cart" invisible="state != 'calculated'"/>
<field name="state" widget="statusbar" statusbar_visible="draft,calculated,rfq_created"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_generate_purchase_rfqs" type="object" class="oe_stat_button" icon="fa-shopping-cart">
<field name="purchase_count" widget="statinfo" string="Purchase Orders"/>
</button>
</div>
<div class="oe_title">
<label for="name" string="MRP Calculation No"/>
<h1><field name="name"/></h1>
</div>
<group>
<group string="Order &amp; BOM">
<field name="production_plan_id"/>
<field name="style_id"/>
<field name="bom_id"/>
</group>
<group string="Quantity &amp; Shortage Budget">
<field name="order_qty"/>
<field name="total_shortage_cost" widget="monetary" class="fw-bold fs-4 text-danger"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<notebook>
<page string="Material Shortage &amp; Requirement Breakdown" name="lines_page">
<field name="line_ids">
<list editable="bottom"
decoration-danger="shortage_qty &gt; 0"
decoration-success="shortage_qty == 0">
<field name="material_type"/>
<field name="name"/>
<field name="product_id"/>
<field name="required_qty"/>
<field name="uom_name"/>
<field name="available_qty"/>
<field name="shortage_qty" class="fw-bold"/>
<field name="to_purchase_qty"/>
<field name="unit_price"/>
<field name="shortage_cost" sum="Total Estimated Shortage Cost"/>
</list>
</field>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- Action & Menu -->
<record id="action_garment_mrp_requirement" model="ir.actions.act_window">
<field name="name">Material Requirements (MRP)</field>
<field name="res_model">garment.mrp.requirement</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_garment_mrp_requirement" name="Material Requirements (MRP)" parent="menu_garment_planning_root" action="action_garment_mrp_requirement" sequence="30"/>
</odoo>

View File

@ -0,0 +1,158 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Carton Views -->
<record id="view_garment_carton_list" model="ir.ui.view">
<field name="name">garment.carton.list</field>
<field name="model">garment.carton</field>
<field name="arch" type="xml">
<list string="Master Cartons" sample="1">
<field name="carton_no"/>
<field name="name"/>
<field name="barcode"/>
<field name="style_id"/>
<field name="total_pieces" sum="Total Garments"/>
<field name="gross_weight_kg" sum="Total Gross Kg"/>
<field name="cbm" sum="Total CBM"/>
<field name="packing_plan_id"/>
<field name="dispatch_id"/>
</list>
</field>
</record>
<record id="view_garment_carton_form" model="ir.ui.view">
<field name="name">garment.carton.form</field>
<field name="model">garment.carton</field>
<field name="arch" type="xml">
<form string="Master Carton">
<sheet>
<div class="oe_title">
<label for="name" string="Carton Identifier"/>
<h1><field name="name"/> (Box #<field name="carton_no"/>)</h1>
<h3>Shipping Barcode / QR: <field name="barcode" class="text-primary"/></h3>
</div>
<group>
<group string="Style &amp; Plan">
<field name="packing_plan_id"/>
<field name="style_id"/>
<field name="dispatch_id"/>
</group>
<group string="Dimensions &amp; CBM">
<label for="length_cm" string="Dimensions (L x W x H) cm"/>
<div class="o_row">
<field name="length_cm"/> × <field name="width_cm"/> × <field name="height_cm"/> cm
</div>
<field name="cbm" class="fw-bold"/>
<field name="net_weight_kg"/>
<field name="gross_weight_kg" class="fw-bold text-success"/>
</group>
</group>
<notebook>
<page string="Garment Contents Breakdown (Size × Color)" name="carton_lines">
<field name="line_ids">
<list editable="bottom">
<field name="color_id"/>
<field name="size_id"/>
<field name="quantity" sum="Total Pieces in Box"/>
</list>
</field>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<record id="action_garment_carton" model="ir.actions.act_window">
<field name="name">Master Cartons</field>
<field name="res_model">garment.carton</field>
<field name="view_mode">list,form</field>
</record>
<!-- Packing Plan Views -->
<record id="view_garment_packing_plan_list" model="ir.ui.view">
<field name="name">garment.packing.plan.list</field>
<field name="model">garment.packing.plan</field>
<field name="arch" type="xml">
<list string="Packing Plans" sample="1"
decoration-info="state == 'in_packing'"
decoration-success="state == 'completed'">
<field name="name"/>
<field name="production_plan_id"/>
<field name="sale_order_id"/>
<field name="style_id"/>
<field name="packing_type"/>
<field name="target_pack_qty"/>
<field name="total_packed_qty" sum="Total Packed Pcs"/>
<field name="total_cartons" sum="Total Cartons"/>
<field name="total_cbm" sum="Total CBM"/>
<field name="total_gross_weight" sum="Total Gross Weight Kg"/>
<field name="state" widget="badge"
decoration-info="state == 'in_packing'"
decoration-success="state == 'completed'"/>
</list>
</field>
</record>
<record id="view_garment_packing_plan_form" model="ir.ui.view">
<field name="name">garment.packing.plan.form</field>
<field name="model">garment.packing.plan</field>
<field name="arch" type="xml">
<form string="Packing Plan">
<header>
<button name="action_start_packing" string="Start Packing" type="object" class="btn-primary" invisible="state != 'draft'"/>
<button name="action_transfer_to_finished_goods" string="Transfer to Finished Goods (Complete)" type="object" class="btn-success" icon="fa-check-circle" invisible="state != 'in_packing'"/>
<field name="state" widget="statusbar" statusbar_visible="draft,in_packing,completed"/>
</header>
<sheet>
<div class="oe_title">
<label for="name" string="Packing Plan Number"/>
<h1><field name="name"/></h1>
</div>
<group>
<group string="Order &amp; Packaging Specs">
<field name="production_plan_id"/>
<field name="sale_order_id"/>
<field name="style_id"/>
<field name="packing_type"/>
</group>
<group string="Shipping Volume &amp; Weights">
<field name="target_pack_qty"/>
<field name="total_packed_qty" class="fw-bold fs-4 text-success"/>
<field name="total_cartons" class="fw-bold"/>
<field name="total_cbm"/>
<field name="total_gross_weight"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<notebook>
<page string="Master Cartons Breakdown" name="cartons_page">
<field name="carton_ids">
<list>
<field name="carton_no"/>
<field name="name"/>
<field name="barcode"/>
<field name="total_pieces" sum="Total Pieces"/>
<field name="gross_weight_kg"/>
<field name="cbm"/>
</list>
</field>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<record id="action_garment_packing_plan" model="ir.actions.act_window">
<field name="name">Packing Plans</field>
<field name="res_model">garment.packing.plan</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_garment_packing_plan" name="Packing Plans" parent="menu_garment_packing_root" action="action_garment_packing_plan" sequence="10"/>
<menuitem id="menu_garment_carton" name="Master Cartons" parent="menu_garment_packing_root" action="action_garment_carton" sequence="20"/>
</odoo>

View File

@ -0,0 +1,196 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Production Plan Kanban View -->
<record id="view_garment_production_plan_kanban" model="ir.ui.view">
<field name="name">garment.production.plan.kanban</field>
<field name="model">garment.production.plan</field>
<field name="arch" type="xml">
<kanban default_group_by="state" sample="1">
<field name="id"/>
<field name="name"/>
<field name="style_id"/>
<field name="planned_qty"/>
<field name="cut_qty"/>
<field name="packed_qty"/>
<field name="progress_pct"/>
<field name="state"/>
<field name="priority"/>
<templates>
<t t-name="card">
<div class="p-2">
<div class="d-flex justify-content-between align-items-center mb-1">
<span class="fw-bold fs-6 text-primary"><field name="name"/></span>
<field name="priority" widget="priority"/>
</div>
<div class="fw-semibold text-dark mb-1"><field name="style_id"/></div>
<div class="text-muted small mb-2">
Planned: <field name="planned_qty"/> Pcs · Cut: <field name="cut_qty"/> Pcs
</div>
<!-- Progress Bar -->
<div class="progress mb-2" style="height: 7px;">
<div class="progress-bar bg-success" role="progressbar" t-attf-style="width: #{record.progress_pct.raw_value}%;"></div>
</div>
<div class="d-flex justify-content-between text-muted small">
<span>Packed: <field name="packed_qty"/></span>
<span class="fw-bold text-success"><field name="progress_pct"/>%</span>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- Production Plan List View -->
<record id="view_garment_production_plan_list" model="ir.ui.view">
<field name="name">garment.production.plan.list</field>
<field name="model">garment.production.plan</field>
<field name="arch" type="xml">
<list string="Production Plans" sample="1"
decoration-info="state == 'planned'"
decoration-warning="state == 'in_progress'"
decoration-success="state == 'completed'"
decoration-muted="state == 'cancelled'">
<field name="name"/>
<field name="sale_order_id"/>
<field name="style_id"/>
<field name="planned_qty" sum="Total Planned"/>
<field name="cut_qty" sum="Total Cut"/>
<field name="sewn_qty" sum="Total Sewn"/>
<field name="packed_qty" sum="Total Packed"/>
<field name="progress_pct" widget="progressbar"/>
<field name="date_start"/>
<field name="date_delivery"/>
<field name="priority" widget="priority"/>
<field name="state" widget="badge"
decoration-info="state == 'planned'"
decoration-warning="state == 'in_progress'"
decoration-success="state == 'completed'"/>
</list>
</field>
</record>
<!-- Production Plan Form View -->
<record id="view_garment_production_plan_form" model="ir.ui.view">
<field name="name">garment.production.plan.form</field>
<field name="model">garment.production.plan</field>
<field name="arch" type="xml">
<form string="Production Plan">
<header>
<button name="action_plan" string="Confirm &amp; Schedule" type="object" class="btn-primary" invisible="state != 'draft'"/>
<button name="action_start" string="Start Production" type="object" class="btn-success" invisible="state != 'planned'"/>
<button name="action_create_cutting_plan" string="Create Cutting Order" type="object" class="btn-info" invisible="state not in ['planned', 'in_progress']"/>
<button name="action_create_mrp_requirement" string="Calculate Material Needs (MRP)" type="object" class="btn-secondary" invisible="state not in ['planned', 'in_progress']"/>
<button name="action_create_packing_plan" string="Create Packing Plan" type="object" class="btn-secondary" invisible="state not in ['in_progress']"/>
<button name="action_complete" string="Mark as Completed" type="object" class="btn-success" invisible="state != 'in_progress'"/>
<button name="action_cancel" string="Cancel Plan" type="object" invisible="state in ['completed', 'cancelled']"/>
<button name="action_reset_to_draft" string="Reset to Draft" type="object" invisible="state != 'cancelled'"/>
<field name="state" widget="statusbar" statusbar_visible="draft,planned,in_progress,completed"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_view_cutting_plans" type="object" class="oe_stat_button" icon="fa-scissors">
<field name="cutting_count" widget="statinfo" string="Cutting"/>
</button>
<button name="action_view_bundles" type="object" class="oe_stat_button" icon="fa-tags">
<field name="bundle_count" widget="statinfo" string="Bundles"/>
</button>
<button name="action_view_jobwork" type="object" class="oe_stat_button" icon="fa-truck">
<field name="jobwork_count" widget="statinfo" string="Job Work"/>
</button>
<button name="action_view_qc" type="object" class="oe_stat_button" icon="fa-check-circle">
<field name="qc_count" widget="statinfo" string="QC Audit"/>
</button>
<button name="action_view_packing" type="object" class="oe_stat_button" icon="fa-archive">
<field name="packing_count" widget="statinfo" string="Packing"/>
</button>
</div>
<div class="oe_title">
<label for="name" string="Production Plan Number"/>
<h1><field name="name"/></h1>
</div>
<group>
<group string="Order &amp; Product">
<field name="sale_order_id"/>
<field name="style_id"/>
<field name="bom_id"/>
<field name="priority" widget="priority"/>
</group>
<group string="Schedule &amp; Dates">
<field name="date_start"/>
<field name="date_delivery"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
</group>
<group string="Manufacturing Progress Tracker">
<group>
<field name="planned_qty" class="fw-bold"/>
<field name="cut_qty"/>
<field name="sewn_qty"/>
</group>
<group>
<field name="packed_qty"/>
<field name="dispatched_qty"/>
<field name="progress_pct" widget="progressbar"/>
</group>
</group>
<notebook>
<page string="Size × Color Breakdown Targets" name="breakdown_page">
<field name="line_ids">
<list editable="bottom">
<field name="color_id"/>
<field name="size_id"/>
<field name="planned_qty" sum="Total Planned"/>
<field name="cut_qty" sum="Total Cut"/>
<field name="sewn_qty" sum="Total Sewn"/>
<field name="packed_qty" sum="Total Packed"/>
<field name="variance" sum="Total Balance"/>
</list>
</field>
</page>
<page string="Cutting Orders" name="cutting_tab">
<field name="cutting_plan_ids" readonly="1">
<list>
<field name="name"/>
<field name="color_id"/>
<field name="plies_count"/>
<field name="planned_cut_qty"/>
<field name="actual_cut_qty"/>
<field name="cutting_wastage_pct"/>
<field name="state"/>
</list>
</field>
</page>
<page string="Packing &amp; Cartons" name="packing_tab">
<field name="packing_plan_ids" readonly="1">
<list>
<field name="name"/>
<field name="packing_type"/>
<field name="total_cartons"/>
<field name="total_packed_qty"/>
<field name="total_cbm"/>
<field name="state"/>
</list>
</field>
</page>
<page string="Notes &amp; Floor Directives" name="notes_tab">
<field name="notes" placeholder="Enter specific line allocation, needle size, thread brand or buyer packing directives..."/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- Action & Menu -->
<record id="action_garment_production_plan" model="ir.actions.act_window">
<field name="name">Production Plans</field>
<field name="res_model">garment.production.plan</field>
<field name="view_mode">kanban,list,form</field>
</record>
<menuitem id="menu_garment_production_plan" name="Production Plans" parent="menu_garment_planning_root" action="action_garment_production_plan" sequence="20"/>
</odoo>

View File

@ -0,0 +1,127 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Defect Type Views -->
<record id="view_garment_defect_type_list" model="ir.ui.view">
<field name="name">garment.defect.type.list</field>
<field name="model">garment.defect.type</field>
<field name="arch" type="xml">
<list string="Defect Categories">
<field name="code"/>
<field name="name"/>
<field name="category"/>
<field name="severity" widget="badge"/>
<field name="active"/>
</list>
</field>
</record>
<record id="action_garment_defect_type" model="ir.actions.act_window">
<field name="name">Quality Defect Master</field>
<field name="res_model">garment.defect.type</field>
<field name="view_mode">list,form</field>
</record>
<!-- Quality Inspection Views -->
<record id="view_garment_quality_inspection_list" model="ir.ui.view">
<field name="name">garment.quality.inspection.list</field>
<field name="model">garment.quality.inspection</field>
<field name="arch" type="xml">
<list string="Quality Audits" sample="1"
decoration-success="verdict == 'passed'"
decoration-warning="verdict == 'rework_needed'"
decoration-danger="verdict == 'rejected'">
<field name="name"/>
<field name="inspection_stage"/>
<field name="production_plan_id"/>
<field name="style_id"/>
<field name="color_id"/>
<field name="size_id"/>
<field name="sample_size" sum="Total Sampled"/>
<field name="passed_qty" sum="Total Passed"/>
<field name="failed_qty" sum="Total Defective"/>
<field name="rework_qty" sum="Total Rework"/>
<field name="inspector_id"/>
<field name="date_inspected"/>
<field name="verdict" widget="badge"
decoration-success="verdict == 'passed'"
decoration-warning="verdict == 'rework_needed'"
decoration-danger="verdict == 'rejected'"/>
</list>
</field>
</record>
<record id="view_garment_quality_inspection_form" model="ir.ui.view">
<field name="name">garment.quality.inspection.form</field>
<field name="model">garment.quality.inspection</field>
<field name="arch" type="xml">
<form string="Quality Audit &amp; Inspection">
<header>
<button name="action_pass" string="Approve (Pass Audit)" type="object" class="btn-success" invisible="verdict == 'passed'"/>
<button name="action_trigger_rework" string="Create Rework Order" type="object" class="btn-warning" icon="fa-wrench" invisible="verdict == 'rework_needed'"/>
<field name="verdict" widget="statusbar" statusbar_visible="passed,rework_needed,rejected"/>
</header>
<sheet>
<div class="oe_button_box" name="button_box">
<button name="action_trigger_rework" type="object" class="oe_stat_button" icon="fa-wrench">
<field name="rework_count" widget="statinfo" string="Rework Orders"/>
</button>
</div>
<div class="oe_title">
<label for="name" string="Inspection Audit No"/>
<h1><field name="name"/></h1>
</div>
<group>
<group string="Inspection Point">
<field name="inspection_stage"/>
<field name="production_plan_id"/>
<field name="style_id"/>
<field name="color_id"/>
<field name="size_id"/>
<field name="bundle_id"/>
<field name="sewing_line_id"/>
</group>
<group string="Auditor &amp; Sample Quantities">
<field name="inspector_id"/>
<field name="date_inspected"/>
<field name="total_lot_qty"/>
<field name="sample_size"/>
<field name="passed_qty" class="fw-bold text-success"/>
<field name="failed_qty" class="fw-bold text-danger"/>
<field name="rework_qty" class="fw-bold text-warning"/>
<field name="scrapped_qty"/>
</group>
</group>
<notebook>
<page string="Defect Log Breakdown" name="defects_tab">
<field name="defect_line_ids">
<list editable="bottom">
<field name="defect_type_id"/>
<field name="category"/>
<field name="defect_count" sum="Total Defects"/>
<field name="action_type"/>
<field name="notes"/>
</list>
</field>
</page>
<page string="Inspector Notes &amp; Root Cause" name="notes_tab">
<field name="remarks" placeholder="Root cause analysis (e.g. blunt needle on Overlock machine 4, improper operator tension, fabric slub)..."/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- Action & Menu -->
<record id="action_garment_quality_inspection" model="ir.actions.act_window">
<field name="name">Quality Inspections</field>
<field name="res_model">garment.quality.inspection</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_garment_quality_inspection" name="Quality Inspections (QC)" parent="menu_garment_quality_root" action="action_garment_quality_inspection" sequence="10"/>
<menuitem id="menu_garment_defect_type" name="Defect Categories Master" parent="menu_garment_quality_root" action="action_garment_defect_type" sequence="30"/>
</odoo>

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Rework List View -->
<record id="view_garment_rework_order_list" model="ir.ui.view">
<field name="name">garment.rework.order.list</field>
<field name="model">garment.rework.order</field>
<field name="arch" type="xml">
<list string="Rework Orders" sample="1"
decoration-info="state == 'assigned'"
decoration-warning="state == 'in_rework'"
decoration-primary="state == 'ready_recheck'"
decoration-success="state == 'passed'"
decoration-danger="state == 'scrapped'">
<field name="name"/>
<field name="inspection_id"/>
<field name="style_id"/>
<field name="color_id"/>
<field name="size_id"/>
<field name="assigned_dept"/>
<field name="assigned_operator_id"/>
<field name="rework_qty" sum="Total Rework Pcs"/>
<field name="passed_rework_qty" sum="Total Restored"/>
<field name="date_assigned"/>
<field name="state" widget="badge"
decoration-info="state == 'assigned'"
decoration-warning="state == 'in_rework'"
decoration-primary="state == 'ready_recheck'"
decoration-success="state == 'passed'"
decoration-danger="state == 'scrapped'"/>
</list>
</field>
</record>
<!-- Rework Form View -->
<record id="view_garment_rework_order_form" model="ir.ui.view">
<field name="name">garment.rework.order.form</field>
<field name="model">garment.rework.order</field>
<field name="arch" type="xml">
<form string="Rework &amp; Alteration Order">
<header>
<button name="action_start_rework" string="Start Alteration" type="object" class="btn-primary" invisible="state != 'assigned'"/>
<button name="action_ready_recheck" string="Submit for QA Re-check" type="object" class="btn-info" invisible="state != 'in_rework'"/>
<button name="action_pass_recheck" string="Approve QA Re-check (Passed)" type="object" class="btn-success" invisible="state != 'ready_recheck'"/>
<button name="action_scrap_rework" string="Mark as Unrepairable Scrap" type="object" class="btn-danger" invisible="state in ['passed', 'scrapped']"/>
<field name="state" widget="statusbar" statusbar_visible="assigned,in_rework,ready_recheck,passed"/>
</header>
<sheet>
<div class="oe_title">
<label for="name" string="Rework Order Number"/>
<h1><field name="name"/></h1>
</div>
<group>
<group string="Source Inspection &amp; Garment">
<field name="inspection_id"/>
<field name="production_plan_id"/>
<field name="style_id"/>
<field name="color_id"/>
<field name="size_id"/>
<field name="bundle_id"/>
</group>
<group string="Assignment &amp; Cost">
<field name="assigned_dept"/>
<field name="assigned_operator_id"/>
<field name="date_assigned"/>
<field name="date_completed"/>
<field name="rework_cost" widget="monetary"/>
</group>
</group>
<group string="Quantities">
<group>
<field name="rework_qty" class="fw-bold fs-4 text-warning"/>
</group>
<group>
<field name="passed_rework_qty" class="fw-bold fs-4 text-success"/>
<field name="scrapped_rework_qty" class="fw-bold fs-4 text-danger"/>
</group>
</group>
<notebook>
<page string="Defect Description &amp; Alteration Steps" name="defect_page">
<field name="defect_description" placeholder="Exact instructions for seam unpicking, panel replacement, re-stitching, or stain treatment..."/>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- Action & Menu -->
<record id="action_garment_rework_order" model="ir.actions.act_window">
<field name="name">Rework Orders</field>
<field name="res_model">garment.rework.order</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_garment_rework_order" name="Rework Orders" parent="menu_garment_quality_root" action="action_garment_rework_order" sequence="20"/>
</odoo>

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Inherit Sale Order Form View -->
<record id="view_garment_sale_order_form" model="ir.ui.view">
<field name="name">garment.sale.order.form</field>
<field name="model">sale.order</field>
<field name="inherit_id" ref="sale.view_order_form"/>
<field name="arch" type="xml">
<!-- Smart Button for Production Plans -->
<xpath expr="//div[@name='button_box']" position="inside">
<button name="action_view_production_plans" type="object" class="oe_stat_button" icon="fa-industry" invisible="not is_garment_order">
<field name="production_plan_count" widget="statinfo" string="Production"/>
</button>
</xpath>
<!-- Garment Header Info -->
<xpath expr="//field[@name='partner_id']" position="after">
<field name="is_garment_order" invisible="1"/>
<field name="garment_style_id" options="{'no_create': True}" invisible="not is_garment_order"/>
<field name="buyer_id" invisible="not is_garment_order"/>
<field name="buyer_po_ref" invisible="not is_garment_order"/>
</xpath>
<xpath expr="//field[@name='payment_term_id']" position="after">
<field name="season_id" invisible="not is_garment_order"/>
<field name="ex_factory_date" invisible="not is_garment_order"/>
<field name="shipment_date" invisible="not is_garment_order"/>
<field name="shipment_mode" invisible="not is_garment_order"/>
<field name="destination_port" invisible="not is_garment_order"/>
<field name="total_garment_qty" invisible="not is_garment_order" class="fw-bold fs-5 text-primary"/>
</xpath>
<!-- Garment Order Matrix Page -->
<xpath expr="//notebook" position="inside">
<page string="Garment Order Matrix (Size × Color)" name="order_matrix_page" invisible="not is_garment_order">
<div class="mb-3 d-flex gap-2">
<button name="action_sync_matrix_to_order_lines" string="Sync Matrix to Order Lines" type="object" class="btn btn-primary btn-sm" icon="fa-refresh"/>
<button name="action_create_production_plan" string="Create Production Plan" type="object" class="btn btn-success btn-sm" icon="fa-play"/>
</div>
<separator string="Dynamic Size × Color Breakdown Matrix"/>
<div class="w-100 my-2">
<field name="matrix_html_summary" nolabel="1" class="w-100 d-block"/>
</div>
<separator string="Matrix Detail Entries (Color / Size / Quantity / Unit Price)"/>
<field name="order_matrix_line_ids">
<list editable="bottom">
<field name="color_id"/>
<field name="size_id"/>
<field name="quantity" sum="Total Pieces"/>
<field name="unit_price"/>
<field name="subtotal" sum="Total Amount"/>
</list>
</field>
</page>
</xpath>
</field>
</record>
<!-- Dedicated Garment Sales Orders Action & Menu -->
<record id="action_garment_sales_order" model="ir.actions.act_window">
<field name="name">Garment Sales Orders</field>
<field name="res_model">sale.order</field>
<field name="view_mode">list,form</field>
<field name="domain">[('is_garment_order', '=', True)]</field>
<field name="context">{'default_is_garment_order': True}</field>
</record>
<menuitem id="menu_garment_sales_order" name="Garment Sales Orders" parent="menu_garment_merchandising_root" action="action_garment_sales_order" sequence="20"/>
</odoo>

View File

@ -0,0 +1,204 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Sewing Line Views -->
<record id="view_garment_sewing_line_list" model="ir.ui.view">
<field name="name">garment.sewing.line.list</field>
<field name="model">garment.sewing.line</field>
<field name="arch" type="xml">
<list string="Sewing Lines">
<field name="code"/>
<field name="name"/>
<field name="supervisor_id"/>
<field name="operator_count"/>
<field name="helper_count"/>
<field name="daily_capacity_pcs"/>
<field name="active"/>
</list>
</field>
</record>
<record id="view_garment_sewing_line_form" model="ir.ui.view">
<field name="name">garment.sewing.line.form</field>
<field name="model">garment.sewing.line</field>
<field name="arch" type="xml">
<form string="Sewing Line">
<sheet>
<div class="oe_title">
<label for="name" string="Line Name"/>
<h1><field name="name" placeholder="e.g. Line 01 - T-Shirt Specialist"/></h1>
</div>
<group>
<group>
<field name="code"/>
<field name="supervisor_id"/>
<field name="active"/>
</group>
<group>
<field name="operator_count"/>
<field name="helper_count"/>
<field name="daily_capacity_pcs"/>
</group>
</group>
<notebook>
<page string="Machine Layout &amp; Setup">
<field name="notes" placeholder="Specify workstation layout, machine types (Overlock 4T, Flatlock, SNLS), and maintenance notes..."/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="action_garment_sewing_line" model="ir.actions.act_window">
<field name="name">Sewing Lines</field>
<field name="res_model">garment.sewing.line</field>
<field name="view_mode">list,form</field>
</record>
<!-- Hourly Production Log Views -->
<record id="view_garment_sewing_hourly_list" model="ir.ui.view">
<field name="name">garment.sewing.hourly.list</field>
<field name="model">garment.sewing.hourly</field>
<field name="arch" type="xml">
<list string="Hourly Production Logs" sample="1"
decoration-success="efficiency_badge == 'excellent'"
decoration-warning="efficiency_badge == 'good'"
decoration-danger="efficiency_badge == 'needs_attention'">
<field name="date"/>
<field name="line_id"/>
<field name="style_id"/>
<field name="production_plan_id"/>
<field name="operators_present"/>
<field name="total_target" sum="Total Target"/>
<field name="total_actual" sum="Total Output"/>
<field name="total_defects" sum="Total Defects"/>
<field name="efficiency_pct" widget="progressbar"/>
<field name="efficiency_badge" widget="badge"
decoration-success="efficiency_badge == 'excellent'"
decoration-warning="efficiency_badge == 'good'"
decoration-danger="efficiency_badge == 'needs_attention'"/>
</list>
</field>
</record>
<record id="view_garment_sewing_hourly_form" model="ir.ui.view">
<field name="name">garment.sewing.hourly.form</field>
<field name="model">garment.sewing.hourly</field>
<field name="arch" type="xml">
<form string="Sewing Hourly Production Tracking">
<sheet>
<div class="oe_title">
<label for="name" string="Log Reference"/>
<h1><field name="name"/></h1>
</div>
<group>
<group string="Line &amp; Running Style">
<field name="line_id"/>
<field name="date"/>
<field name="shift"/>
<field name="style_id"/>
<field name="production_plan_id"/>
</group>
<group string="Labor &amp; Line Efficiency Summary">
<field name="operators_present"/>
<field name="sam"/>
<field name="total_worked_hours"/>
<field name="efficiency_pct" class="fw-bold fs-3 text-primary" widget="percentage"/>
<field name="efficiency_badge" widget="badge"
decoration-success="efficiency_badge == 'excellent'"
decoration-warning="efficiency_badge == 'good'"
decoration-danger="efficiency_badge == 'needs_attention'"/>
</group>
</group>
<div class="row my-3 p-3 bg-light rounded text-center">
<div class="col-4">
<span class="text-muted text-uppercase small">Day Target</span>
<div class="fs-4 fw-bold text-dark"><field name="total_target"/> Pcs</div>
</div>
<div class="col-4">
<span class="text-muted text-uppercase small">Actual Produced</span>
<div class="fs-4 fw-bold text-success"><field name="total_actual"/> Pcs</div>
</div>
<div class="col-4">
<span class="text-muted text-uppercase small">Alterations / Defects</span>
<div class="fs-4 fw-bold text-danger"><field name="total_defects"/> Pcs</div>
</div>
</div>
<notebook>
<page string="Hourly Floor Tracker (Target / Actual / Defect)" name="hourly_grid">
<table class="table table-bordered text-center align-middle" style="font-size:13px;">
<thead class="table-dark">
<tr>
<th style="width:14%;">Metric</th>
<th>H1 (08:30-09:30)</th>
<th>H2 (09:30-10:30)</th>
<th>H3 (10:30-11:30)</th>
<th>H4 (11:30-12:30)</th>
<th>H5 (13:15-14:15)</th>
<th>H6 (14:15-15:15)</th>
<th>H7 (15:15-16:15)</th>
<th>H8 (16:15-17:15)</th>
<th class="table-secondary">OT (Overtime)</th>
</tr>
</thead>
<tbody>
<tr class="table-light">
<td class="fw-bold text-start">Target Pcs</td>
<td><field name="h1_target"/></td>
<td><field name="h2_target"/></td>
<td><field name="h3_target"/></td>
<td><field name="h4_target"/></td>
<td><field name="h5_target"/></td>
<td><field name="h6_target"/></td>
<td><field name="h7_target"/></td>
<td><field name="h8_target"/></td>
<td><field name="ot_target"/></td>
</tr>
<tr class="table-success">
<td class="fw-bold text-start text-success">Actual Output</td>
<td><field name="h1_actual"/></td>
<td><field name="h2_actual"/></td>
<td><field name="h3_actual"/></td>
<td><field name="h4_actual"/></td>
<td><field name="h5_actual"/></td>
<td><field name="h6_actual"/></td>
<td><field name="h7_actual"/></td>
<td><field name="h8_actual"/></td>
<td><field name="ot_actual"/></td>
</tr>
<tr class="table-warning">
<td class="fw-bold text-start text-danger">Defects / Alter</td>
<td><field name="h1_defect"/></td>
<td><field name="h2_defect"/></td>
<td><field name="h3_defect"/></td>
<td><field name="h4_defect"/></td>
<td><field name="h5_defect"/></td>
<td><field name="h6_defect"/></td>
<td><field name="h7_defect"/></td>
<td><field name="h8_defect"/></td>
<td><field name="ot_defect"/></td>
</tr>
</tbody>
</table>
</page>
</notebook>
</sheet>
<chatter/>
</form>
</field>
</record>
<!-- Actions & Menus -->
<record id="action_garment_sewing_hourly" model="ir.actions.act_window">
<field name="name">Hourly Production Tracking</field>
<field name="res_model">garment.sewing.hourly</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_garment_sewing_hourly" name="Hourly Production Logs" parent="menu_garment_sewing_root" action="action_garment_sewing_hourly" sequence="10"/>
<menuitem id="menu_garment_sewing_line" name="Sewing Lines Configuration" parent="menu_garment_sewing_root" action="action_garment_sewing_line" sequence="20"/>
</odoo>

32
docker-compose.yml Normal file
View File

@ -0,0 +1,32 @@
name: chennora_odoo
services:
db:
image: postgres:15
environment:
POSTGRES_DB: postgres
POSTGRES_USER: odoo
POSTGRES_PASSWORD: odoo
volumes:
- client51_pgdata:/var/lib/postgresql/data
restart: always
odoo:
image: odoo:19.0
depends_on:
- db
ports:
- "10051:8069"
environment:
HOST: db
USER: odoo
PASSWORD: odoo
LIST_DB: "True"
volumes:
- client51_odoo_data:/var/lib/odoo
- ./addons:/mnt/extra-addons
restart: always
volumes:
client51_pgdata:
client51_odoo_data: