first commit - Week 1: C2C Odoo ERP base setup with multi-company, UoM engine, and core modules

This commit is contained in:
Alaguraj0361 2026-03-28 18:46:53 +05:30
commit 137eb17d4f
19 changed files with 391 additions and 0 deletions

20
.gitignore vendored Normal file
View File

@ -0,0 +1,20 @@
# Docker volumes - don't commit database data
.data/
*.log
# Python
__pycache__/
*.pyc
*.pyo
# Odoo compiled assets
/addons/**/static/lib/
/addons/**/*.pyc
# Temp scripts
check_removal.py
verify_week1.py
verify.log
uom_bad.log
verify_error.log
config_odoo.py

View File

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

View File

@ -0,0 +1,24 @@
# -*- coding: utf-8 -*-
{
'name': 'C2C Core',
'version': '1.0',
'category': 'Base',
'summary': 'Core configuration for C2C Agricorp implementation',
'description': """
- Configures product and partner categories.
- Adds fields for shelf life, expiry, and quality grades.
- Enables tracking and removal strategies.
""",
'author': 'Antigravity',
'depends': ['stock', 'purchase', 'sale_management', 'mrp', 'product_expiry'],
'data': [
'data/partner_data.xml',
'data/product_category_data.xml',
'data/stock_location_data.xml',
'views/product_template_views.xml',
'views/res_partner_views.xml',
],
'installable': True,
'application': True,
'license': 'LGPL-3',
}

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Standard Odoo categories for farmers, wholesalers, etc. -->
<record id="res_partner_category_farmer" model="res.partner.category">
<field name="name">Farmers</field>
</record>
<record id="res_partner_category_wholesaler" model="res.partner.category">
<field name="name">Wholesalers</field>
</record>
<record id="res_partner_category_retail" model="res.partner.category">
<field name="name">Retail Customers</field>
</record>
<record id="res_partner_category_logistics" model="res.partner.category">
<field name="name">Logistics Providers</field>
</record>
<record id="res_partner_category_intercompany" model="res.partner.category">
<field name="name">Intercompany Partners</field>
</record>
</odoo>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="product_category_raw" model="product.category">
<field name="name">Raw Materials</field>
<field name="removal_strategy_id" ref="product_expiry.removal_fefo"/>
</record>
<record id="product_category_semi" model="product.category">
<field name="name">Semi-finished Goods</field>
<field name="removal_strategy_id" ref="product_expiry.removal_fefo"/>
</record>
<record id="product_category_finished" model="product.category">
<field name="name">Finished Goods</field>
<field name="removal_strategy_id" ref="product_expiry.removal_fefo"/>
</record>
</odoo>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- We need to create locations for each company?
Or just general ones for the active company?
Multi-company setup requires company_id usually.
Let's create generic ones if possible or use a script.
-->
<record id="stock_location_qc" model="stock.location">
<field name="name">QC Hold</field>
<field name="location_id" ref="stock.stock_location_locations"/>
<field name="usage">internal</field>
</record>
<record id="stock_location_rejected" model="stock.location">
<field name="name">Rejected/Scrap</field>
<field name="location_id" ref="stock.stock_location_locations"/>
<field name="usage">inventory</field>
<field name="scrap_location">True</field>
</record>
</odoo>

View File

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

View File

@ -0,0 +1,25 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api
class ProductTemplate(models.Model):
_inherit = 'product.template'
quality_grade = fields.Selection([
('a', 'Grade A'),
('b', 'Grade B'),
('c', 'Grade C'),
], string='Quality Grade', default='a')
# Many expiry fields are in product.template if product_expiry is installed
# shelf life (expiration time in days), etc.
class ResPartner(models.Model):
_inherit = 'res.partner'
partner_category_type = fields.Selection([
('farmer', 'Farmer'),
('wholesaler', 'Wholesaler'),
('retail_customer', 'Retail Customer'),
('logistics', 'Logistics'),
('intercompany', 'Intercompany'),
], string='C2C Partner Category')

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="product_template_form_view_inherit_c2c" model="ir.ui.view">
<field name="name">product.template.form.inherit.c2c</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_form_view"/>
<field name="arch" type="xml">
<field name="categ_id" position="after">
<field name="quality_grade"/>
</field>
</field>
</record>
</odoo>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="res_partner_view_form_inherit_c2c" model="ir.ui.view">
<field name="name">res.partner.form.inherit.c2c</field>
<field name="model">res.partner</field>
<field name="inherit_id" ref="base.view_partner_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='category_id']" position="after">
<field name="partner_category_type"/>
</xpath>
</field>
</record>
</odoo>

View File

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

View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
{
'name': 'C2C UoM Engine',
'version': '1.0',
'category': 'Inventory/Inventory',
'summary': 'Multi-stage UoM conversion framework for Clickstocart',
'description': """
Stores conversion rules per product category/category.
Provides a server-side conversion utility for complex UoM constraints.
""",
'author': 'Antigravity',
'depends': ['stock'],
'data': [
'security/ir.model.access.csv',
'data/uom_data.xml',
'views/uom_conversion_rule_views.xml',
],
'installable': True,
'application': False,
'license': 'LGPL-3',
}

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- Weight Categories - Use existing or new? Standard typically has 'Weight' -->
<!-- Let's ensure these specific ones exist -->
<record id="product_uom_500g" model="uom.uom">
<field name="name">500g</field>
<field name="category_id" ref="uom.product_uom_categ_kgm"/>
<field name="uom_type">smaller</field>
<field name="factor">2.0</field> <!-- 1kg / 2 = 0.5kg -->
</record>
<record id="product_uom_100g" model="uom.uom">
<field name="name">100g</field>
<field name="category_id" ref="uom.product_uom_categ_kgm"/>
<field name="uom_type">smaller</field>
<field name="factor">10.0</field> <!-- 1kg / 10 = 0.1kg -->
</record>
<record id="product_uom_bundle_3m" model="uom.uom">
<field name="name">Bundle (3m)</field>
<field name="category_id" ref="uom.uom_categ_length"/>
<field name="uom_type">bigger</field>
<field name="factor_inv">3.0</field> <!-- 1m * 3 = 3m -->
</record>
</odoo>

View File

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

View File

@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.exceptions import UserError
class UoMConversionRule(models.Model):
_name = 'c2c.uom.conversion.rule'
_description = 'C2C UoM Conversion Rule'
name = fields.Char(string='Rule Name', required=True)
product_category_id = fields.Many2one('product.category', string='Product Category')
from_uom_id = fields.Many2one('uom.uom', string='From UoM', required=True)
to_uom_id = fields.Many2one('uom.uom', string='To UoM', required=True)
ratio = fields.Float(string='Conversion Ratio', default=1.0, required=True, digits=(16, 4))
description = fields.Text(string='Description')
@api.model
def convert(self, product, quantity, from_uom, to_uom):
"""
Utility method to convert quantities based on local rules if they exist.
Otherwise falls back to standard Odoo UoM conversion.
"""
rule = self.search([
('product_category_id', '=', product.categ_id.id),
('from_uom_id', '=', from_uom.id),
('to_uom_id', '=', to_uom.id)
], limit=1)
if rule:
return quantity * rule.ratio
# Fallback to Odoo standard
return from_uom._compute_quantity(quantity, to_uom)

View File

@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_c2c_uom_conversion_rule,c2c.uom.conversion.rule,model_c2c_uom_conversion_rule,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_c2c_uom_conversion_rule c2c.uom.conversion.rule model_c2c_uom_conversion_rule base.group_user 1 1 1 1

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_c2c_uom_conversion_rule_tree" model="ir.ui.view">
<field name="name">c2c.uom.conversion.rule.tree</field>
<field name="model">c2c.uom.conversion.rule</field>
<field name="arch" type="xml">
<tree string="UoM Conversion Rules" editable="bottom">
<field name="name"/>
<field name="product_category_id"/>
<field name="from_uom_id"/>
<field name="to_uom_id"/>
<field name="ratio"/>
<field name="description"/>
</tree>
</field>
</record>
<record id="action_c2c_uom_conversion_rule" model="ir.actions.act_window">
<field name="name">Conversion Rules</field>
<field name="res_model">c2c.uom.conversion.rule</field>
<field name="view_mode">list,form</field>
</record>
<menuitem id="menu_c2c_uom_engine"
name="UoM Engine"
parent="stock.menu_stock_config"
sequence="100"/>
<menuitem id="menu_c2c_uom_conversion_rule"
name="Conversion Rules"
parent="menu_c2c_uom_engine"
action="action_c2c_uom_conversion_rule"
sequence="10"/>
</odoo>

33
docker-compose.yml Normal file
View File

@ -0,0 +1,33 @@
version: "3.8"
services:
db:
image: postgres:15
container_name: odoo_clickstocart_db
environment:
POSTGRES_DB: postgres
POSTGRES_USER: odoo
POSTGRES_PASSWORD: odoo
volumes:
- client1_pgdata:/var/lib/postgresql/data
restart: always
odoo:
image: odoo:17.0
container_name: odoo_clickstocart
depends_on:
- db
ports:
- "10005:8069"
environment:
HOST: db
USER: odoo
PASSWORD: odoo
volumes:
- client1_odoo_data:/var/lib/odoo
- ./addons:/mnt/extra-addons
restart: always
volumes:
client1_pgdata:
client1_odoo_data:

84
weekone.md Normal file
View File

@ -0,0 +1,84 @@
# Week 1 Implementation Guide & Verification Manual
This document is your complete step-by-step guide on how to manually verify and test all the Week 1 features directly within the Odoo user interface.
---
## 1. How to Check: Multi-Company Architecture
**Goal**: Verify that all 3 companies and their isolated warehouses exist.
**Instructions**:
1. Login to `NewClickstoCart` as the administrator.
2. Look at the top-right corner of the top navigation bar. Click on the **Company Switcher** (the building icon next to your profile picture).
3. **Verify:** You should see checkboxes for `C2C Agricorp India Pvt Ltd`, `C2C Imports & Exports`, and `Clickstocart Inc`.
4. Click on the **Settings app** > **Users & Companies** > **Companies**.
5. **Verify:** The 3 companies are listed here with their respective details.
6. Still in Settings, navigate to **Users & Companies** > **Groups**.
7. **Verify:** Search for "Manufacturing Manager", "Warehouse Staff", etc. You will see these roles exist for assigning to future employees.
8. Open the **Inventory app** > **Configuration** > **Warehouses**.
9. **Verify:** You should see one distinct warehouse per company (e.g., `C2C - C2C Agricorp`, `C2C - Clickstocart`).
---
## 2. How to Check: Master Data Fields (Products & Partners)
**Goal**: Verify custom fields and categorized data templates.
**Instructions (Partners/Contacts)**:
1. Open the **Contacts app**.
2. Click **New** to create a test contact.
3. In the form, look closely below the tags/categories area or in the main tabs.
4. **Verify:** You will see a new dropdown field called **C2C Partner Category**. Click it to ensure options like `Farmer`, `Wholesaler`, `Retail Customer` exist.
**Instructions (Products)**:
1. Open the **Inventory app** > **Products** > **Products**.
2. Click **New**.
3. Under the **General Information** page, click the **Product Category** dropdown.
4. **Verify:** You will see categories explicitly created for us: `Raw Materials`, `Semi-finished Goods`, and `Finished Goods`.
5. Still on the product form, look at the bottom of the main fields.
6. **Verify:** There is a new dropdown called **Quality Grade** containing `Grade A`, `Grade B`, `Grade C`.
7. Go to the **Inventory tab** on the product form.
8. **Verify:** Under **Traceability**, ensure the **By Lots** option is available. Check the **Expiration Date** box to see the `Shelf Life` fields appear.
---
## 3. How to Check: UoM Engine
**Goal**: Test the new complex conversion engine and the new standard units.
**Instructions**:
1. Open the **Inventory app**.
2. Navigate to **Configuration** > **Units of Measure** (Check 'UoM Categories' if 'Units of Measure' isn't directly visible).
3. **Verify:** Search for `500g`, `100g`, or `Bundle (3m)`. They are fully registered in the standard weight/length systems.
4. Navigate to **Configuration** > **Conversion Rules** (Under the new "UoM Engine" section).
5. **Verify:** This is the new custom dashboard. Click **New**. You can now define a rule that says "For Category X, converting from Y to Z uses Ratio R".
---
## 4. How to Check: Batch/Expiry & QC Workflow
**Goal**: Verify that Odoo enforces FEFO (First Expired First Out) and has a location to hold quality checks.
**Instructions (QC Locations)**:
1. In the **Inventory app**, turn on Developer Mode if you can't see the Locations menu.
2. Go to **Configuration** > **Locations**.
3. **Verify:** Remove the "Internal" filter in the search bar and search for `QC Hold`. You will see it exists as an Internal Location.
4. **Verify:** Search for `Rejected/Scrap`. It exists and has the flag "Is a Scrap Location" checked.
**Instructions (FEFO Strategy)**:
1. Go to **Inventory app** > **Configuration** > **Product Categories**.
2. Open the `Raw Materials` category.
3. **Verify:** Scroll down to the Logistics section. The **Force Removal Strategy** is set to `First Expired, First Out (FEFO)`.
---
## 5. End-to-End Test: The Farmer Procurement Flow
To see everything working together, perform this exact sequence:
1. **Prep**: Create a Product named "Test Turmeric", set tracking to "By Lots", and check "Expiration Date" (give it 100 days shelf life).
2. **Purchase**: Open the **Purchase app**. Select a vendor/Farmer. Add "Test Turmeric" and click **Confirm Order**.
3. **Receipt**: Click the **Receive Products** button in the top right of the Purchase Order.
4. **Create Lot**: On the receipt line for Turmeric, you cannot validate yet. Click the **List icon (Options/Details)** on the right side of the product line.
5. Under "Assign Serial/Lot Number", type in `LOT-001` and set an Expiration Date for tomorrow. Apply and **Validate** the receipt.
6. **QC Transfer**: Open the **Inventory app** > **Operations** > **Transfers**. Create a new internal transfer.
7. Set the Source Location as `WH/Stock` (where the goods just arrived) and the Destination Location to `QC Hold`.
8. Add the Product, select `LOT-001`, and **Validate**.
### **Congratulations!** You have just executed a full cycle of Procurement -> Quality Control with multi-stage Lot tracking and Expiry enforcement.