first commit
This commit is contained in:
commit
c365322e3d
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal 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
427
WORKFLOW_GUIDELINE.md
Normal 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 1–25, Ply 26–50) 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.*
|
||||
2
addons/garment_erp/__init__.py
Normal file
2
addons/garment_erp/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import models
|
||||
142
addons/garment_erp/__manifest__.py
Normal file
142
addons/garment_erp/__manifest__.py
Normal 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,
|
||||
}
|
||||
132
addons/garment_erp/data/garment_sequence_data.xml
Normal file
132
addons/garment_erp/data/garment_sequence_data.xml
Normal 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>
|
||||
20
addons/garment_erp/models/__init__.py
Normal file
20
addons/garment_erp/models/__init__.py
Normal 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
|
||||
109
addons/garment_erp/models/bundle.py
Normal file
109
addons/garment_erp/models/bundle.py
Normal 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')
|
||||
212
addons/garment_erp/models/cutting_plan.py
Normal file
212
addons/garment_erp/models/cutting_plan.py
Normal 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
|
||||
81
addons/garment_erp/models/dispatch.py
Normal file
81
addons/garment_erp/models/dispatch.py
Normal 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'})
|
||||
101
addons/garment_erp/models/fabric_inspection.py
Normal file
101
addons/garment_erp/models/fabric_inspection.py
Normal 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'})
|
||||
95
addons/garment_erp/models/fabric_roll.py
Normal file
95
addons/garment_erp/models/fabric_roll.py
Normal 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',
|
||||
}
|
||||
35
addons/garment_erp/models/finished_goods.py
Normal file
35
addons/garment_erp/models/finished_goods.py
Normal 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)
|
||||
152
addons/garment_erp/models/garment_base.py
Normal file
152
addons/garment_erp/models/garment_base.py
Normal 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!')
|
||||
199
addons/garment_erp/models/garment_bom.py
Normal file
199
addons/garment_erp/models/garment_bom.py
Normal 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
|
||||
100
addons/garment_erp/models/garment_costing.py
Normal file
100
addons/garment_erp/models/garment_costing.py
Normal 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
|
||||
72
addons/garment_erp/models/garment_dashboard.py
Normal file
72
addons/garment_erp/models/garment_dashboard.py
Normal 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)])
|
||||
172
addons/garment_erp/models/garment_style.py
Normal file
172
addons/garment_erp/models/garment_style.py
Normal 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')
|
||||
136
addons/garment_erp/models/job_work.py
Normal file
136
addons/garment_erp/models/job_work.py
Normal 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)
|
||||
175
addons/garment_erp/models/mrp_requirement.py
Normal file
175
addons/garment_erp/models/mrp_requirement.py
Normal 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
|
||||
143
addons/garment_erp/models/packing.py
Normal file
143
addons/garment_erp/models/packing.py
Normal 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)
|
||||
265
addons/garment_erp/models/production_plan.py
Normal file
265
addons/garment_erp/models/production_plan.py
Normal 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
|
||||
124
addons/garment_erp/models/quality_control.py
Normal file
124
addons/garment_erp/models/quality_control.py
Normal 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')
|
||||
70
addons/garment_erp/models/rework.py
Normal file
70
addons/garment_erp/models/rework.py
Normal 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(),
|
||||
})
|
||||
222
addons/garment_erp/models/sale_order.py
Normal file
222
addons/garment_erp/models/sale_order.py
Normal 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
|
||||
134
addons/garment_erp/models/sewing_line.py
Normal file
134
addons/garment_erp/models/sewing_line.py
Normal 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'
|
||||
105
addons/garment_erp/report/garment_report_actions.xml
Normal file
105
addons/garment_erp/report/garment_report_actions.xml
Normal 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 & 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 & 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>
|
||||
54
addons/garment_erp/report/report_bundle_barcode_template.xml
Normal file
54
addons/garment_erp/report/report_bundle_barcode_template.xml
Normal 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&value=%s&width=450&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>
|
||||
93
addons/garment_erp/report/report_cutting_plan_template.xml
Normal file
93
addons/garment_erp/report/report_cutting_plan_template.xml
Normal 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 & 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 & 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 & 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>
|
||||
144
addons/garment_erp/report/report_fabric_inspection_template.xml
Normal file
144
addons/garment_erp/report/report_fabric_inspection_template.xml
Normal 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 & 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"><= <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>
|
||||
126
addons/garment_erp/report/report_garment_costing_template.xml
Normal file
126
addons/garment_erp/report/report_garment_costing_template.xml
Normal 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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 & 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>
|
||||
@ -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 & Seal</strong></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
||||
81
addons/garment_erp/report/report_packing_list_template.xml
Normal file
81
addons/garment_erp/report/report_packing_list_template.xml
Normal 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"/> m³</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 & Logistics Manager</strong></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
||||
@ -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 & 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>
|
||||
@ -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>
|
||||
77
addons/garment_erp/report/report_sales_matrix_template.xml
Normal file
77
addons/garment_erp/report/report_sales_matrix_template.xml
Normal 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>
|
||||
106
addons/garment_erp/security/garment_security.xml
Normal file
106
addons/garment_erp/security/garment_security.xml
Normal 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 & 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 & 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 & 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>
|
||||
104
addons/garment_erp/security/ir.model.access.csv
Normal file
104
addons/garment_erp/security/ir.model.access.csv
Normal 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
|
||||
|
BIN
addons/garment_erp/static/description/icon.png
Normal file
BIN
addons/garment_erp/static/description/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
2
addons/garment_erp/tests/__init__.py
Normal file
2
addons/garment_erp/tests/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import test_e2e_lifecycle
|
||||
564
addons/garment_erp/tests/test_e2e_lifecycle.py
Normal file
564
addons/garment_erp/tests/test_e2e_lifecycle.py
Normal 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))
|
||||
|
||||
165
addons/garment_erp/views/bundle_views.xml
Normal file
165
addons/garment_erp/views/bundle_views.xml
Normal 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 & 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>
|
||||
128
addons/garment_erp/views/cutting_plan_views.xml
Normal file
128
addons/garment_erp/views/cutting_plan_views.xml
Normal 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 & Lay Plan">
|
||||
<header>
|
||||
<button name="action_start_cutting" string="Start Lay & 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 & 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 & 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>
|
||||
106
addons/garment_erp/views/dispatch_views.xml
Normal file
106
addons/garment_erp/views/dispatch_views.xml
Normal 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 & 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 & Sales Order">
|
||||
<field name="customer_id"/>
|
||||
<field name="sale_order_id"/>
|
||||
<field name="production_plan_id"/>
|
||||
<field name="date_dispatch"/>
|
||||
</group>
|
||||
<group string="Carrier & 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 & 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 & 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 & Shipments" parent="menu_garment_packing_root" action="action_garment_dispatch" sequence="30"/>
|
||||
|
||||
</odoo>
|
||||
106
addons/garment_erp/views/fabric_inspection_views.xml
Normal file
106
addons/garment_erp/views/fabric_inspection_views.xml
Normal 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 & Origin">
|
||||
<field name="roll_id"/>
|
||||
<field name="fabric_product_id"/>
|
||||
<field name="supplier_id"/>
|
||||
<field name="shade_lot"/>
|
||||
</group>
|
||||
<group string="Inspector & Audit Date">
|
||||
<field name="inspector_id"/>
|
||||
<field name="date_inspected"/>
|
||||
<field name="inspected_meters"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Physical Verification & 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 (<= 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 (> 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 & 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>
|
||||
150
addons/garment_erp/views/fabric_roll_views.xml
Normal file
150
addons/garment_erp/views/fabric_roll_views.xml
Normal 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 & 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 & 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>
|
||||
84
addons/garment_erp/views/finished_goods_views.xml
Normal file
84
addons/garment_erp/views/finished_goods_views.xml
Normal 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>
|
||||
228
addons/garment_erp/views/garment_base_views.xml
Normal file
228
addons/garment_erp/views/garment_base_views.xml
Normal 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 & 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>
|
||||
114
addons/garment_erp/views/garment_bom_views.xml
Normal file
114
addons/garment_erp/views/garment_bom_views.xml
Normal 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 & 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 & 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 & 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>
|
||||
132
addons/garment_erp/views/garment_costing_views.xml
Normal file
132
addons/garment_erp/views/garment_costing_views.xml
Normal 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 & 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 & 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 & 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 & 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 & Administration">
|
||||
<group>
|
||||
<field name="factory_overhead_pct"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="factory_overhead_cost" widget="monetary"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<notebook>
|
||||
<page string="Costing Notes & 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 & Post Costing" parent="menu_garment_merchandising_root" action="action_garment_costing" sequence="30"/>
|
||||
|
||||
</odoo>
|
||||
163
addons/garment_erp/views/garment_dashboard_views.xml
Normal file
163
addons/garment_erp/views/garment_dashboard_views.xml
Normal 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 & 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>
|
||||
62
addons/garment_erp/views/garment_menus.xml
Normal file
62
addons/garment_erp/views/garment_menus.xml
Normal 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 & MRP"
|
||||
parent="menu_garment_root"
|
||||
sequence="20"/>
|
||||
|
||||
<menuitem id="menu_garment_cutting_root"
|
||||
name="Cutting & 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 & Rework"
|
||||
parent="menu_garment_root"
|
||||
sequence="60"/>
|
||||
|
||||
<menuitem id="menu_garment_inventory_root"
|
||||
name="Fabric & Stores"
|
||||
parent="menu_garment_root"
|
||||
sequence="70"/>
|
||||
|
||||
<menuitem id="menu_garment_packing_root"
|
||||
name="Packing & 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>
|
||||
178
addons/garment_erp/views/garment_style_views.xml
Normal file
178
addons/garment_erp/views/garment_style_views.xml
Normal 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 & 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>
|
||||
21
addons/garment_erp/views/garment_traceability_views.xml
Normal file
21
addons/garment_erp/views/garment_traceability_views.xml
Normal 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>
|
||||
138
addons/garment_erp/views/job_work_views.xml
Normal file
138
addons/garment_erp/views/job_work_views.xml
Normal 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 & Process">
|
||||
<field name="vendor_id"/>
|
||||
<field name="process_type"/>
|
||||
<field name="production_plan_id"/>
|
||||
<field name="style_id"/>
|
||||
</group>
|
||||
<group string="Dates & 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 & 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 & 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>
|
||||
91
addons/garment_erp/views/mrp_requirement_views.xml
Normal file
91
addons/garment_erp/views/mrp_requirement_views.xml
Normal 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 & BOM">
|
||||
<field name="production_plan_id"/>
|
||||
<field name="style_id"/>
|
||||
<field name="bom_id"/>
|
||||
</group>
|
||||
<group string="Quantity & 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 & Requirement Breakdown" name="lines_page">
|
||||
<field name="line_ids">
|
||||
<list editable="bottom"
|
||||
decoration-danger="shortage_qty > 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>
|
||||
158
addons/garment_erp/views/packing_views.xml
Normal file
158
addons/garment_erp/views/packing_views.xml
Normal 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 & Plan">
|
||||
<field name="packing_plan_id"/>
|
||||
<field name="style_id"/>
|
||||
<field name="dispatch_id"/>
|
||||
</group>
|
||||
<group string="Dimensions & 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 & 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 & 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>
|
||||
196
addons/garment_erp/views/production_plan_views.xml
Normal file
196
addons/garment_erp/views/production_plan_views.xml
Normal 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 & 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 & Product">
|
||||
<field name="sale_order_id"/>
|
||||
<field name="style_id"/>
|
||||
<field name="bom_id"/>
|
||||
<field name="priority" widget="priority"/>
|
||||
</group>
|
||||
<group string="Schedule & 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 & 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 & 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>
|
||||
127
addons/garment_erp/views/quality_control_views.xml
Normal file
127
addons/garment_erp/views/quality_control_views.xml
Normal 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 & 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 & 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 & 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>
|
||||
99
addons/garment_erp/views/rework_views.xml
Normal file
99
addons/garment_erp/views/rework_views.xml
Normal 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 & 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 & 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 & 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 & 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>
|
||||
75
addons/garment_erp/views/sale_order_views.xml
Normal file
75
addons/garment_erp/views/sale_order_views.xml
Normal 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>
|
||||
204
addons/garment_erp/views/sewing_line_views.xml
Normal file
204
addons/garment_erp/views/sewing_line_views.xml
Normal 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 & 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 & 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 & 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
32
docker-compose.yml
Normal 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:
|
||||
Loading…
x
Reference in New Issue
Block a user