Skip to content

Conversation

@Aniketkharkia
Copy link
Contributor

@Aniketkharkia Aniketkharkia commented Jan 9, 2026

Summary by CodeRabbit

  • New Features

    • POS branch creation endpoint with branch/location details.
    • company_id added to POS login and order payloads to support multi-company flows.
    • POS client now persists branch listings.
  • Enhancements

    • Session-aware payment-method resolution with better fallbacks for orders.
  • Tests

    • Unit tests for branch creation and expanded integration coverage.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 9, 2026

📝 Walkthrough

Walkthrough

Propagates company_id through POS flows, adds branch creation API and BranchRequest model, updates Odoo POS calls to use dynamic company_id, enhances order creation (payment method resolution and company-aware config selection), and persists branch details. Tests and data model updated accordingly.

Changes

Cohort / File(s) Summary
API Router — POS Endpoints
kairon/api/app/routers/pos.py
Added BranchRequest export and new POST /create/branch endpoint; pos_login now forwards company_id; create_order passes company_id to processor.
POS Models & Public Requests
kairon/shared/pos/models.py
Added BranchRequest model (branch_name, street, city, state). Added company_id: int = 1 to LoginRequest and POSOrderRequest.
POS Processor & Business Logic
kairon/shared/pos/processor.py
Added save_branch_details and create_branch; create_pos_order signature now accepts company_id; select POS config by company_id; resolve payment_method_id from session or fallback; include company_id in order payload; added INDIA_STATE_MAP and validation.
Odoo POS Integration
kairon/pos/odoo/odoo_pos.py
authenticate, products_list, and orders_list now consume/propagate company_id from kwargs and build product/order URLs using company_id instead of hardcoded value.
POS Data Model Storage
kairon/shared/pos/data_objects.py
Added branches field to POSClientDetails (ListField(DictField())) to persist branch entries.
Unit Tests — POS
tests/unit_test/pos_test.py
Added tests for create_branch success, JSON-RPC failure, invalid state handling, and missing POS client config; minor formatting tweaks.
Integration Tests
tests/integration_test/services_test.py
Large integration test additions/updates across services and webhooks (coverage expanded, many new models referenced).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Router as API Router (pos.py)
    participant Processor as POS Processor
    participant Odoo

    Client->>Router: POST /create/branch {branch_name, street, city, state, company_id, session_id}
    Router->>Processor: create_branch(session_id, branch_name, street, city, state, bot, user)
    Processor->>Odoo: JSON-RPC create res.company branch (mapped state id, company_id)
    Odoo-->>Processor: branch_id / error
    Processor->>Processor: save_branch_details(bot, branch_name, company_id, user, pos_type)
    Processor-->>Router: {branch_id, status}
    Router-->>Client: HTTP response
Loading
sequenceDiagram
    participant Client
    participant Router as API Router (pos.py)
    participant Processor as POS Processor
    participant Odoo

    Client->>Router: POST /api/orders/create {products, partner_id, company_id, session_id}
    Router->>Processor: create_pos_order(session_id, products, partner_id, company_id)
    Processor->>Processor: select POS config where config.company_id == company_id
    Processor->>Odoo: retrieve open session/payment_method_ids for session_id
    alt payment_method_ids present
        Processor->>Odoo: create order with selected payment_method_id and company_id
    else payment_method_ids absent
        Processor->>Odoo: fetch available payment methods (fallback to first)
        Processor->>Odoo: create order with resolved payment_method_id and company_id
    end
    Odoo-->>Processor: order created / error
    Processor-->>Router: order response
    Router-->>Client: HTTP response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • hiteshghuge
  • sushantpatade

Poem

🐰 A hop, a nibble, a branch in place,
company_id tucked in each little space.
States mapped true, payments found and stored,
rabbit cheers for orders and branches adored! 🌿

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'odoo multiple branch option' directly addresses the main objective of the PR, which is to add support for multiple branches in Odoo POS operations, as evidenced by the comprehensive changes across models, processors, and APIs that enable branch creation and company_id propagation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ac0c3ff and e69ba73.

📒 Files selected for processing (1)
  • tests/unit_test/pos_test.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/unit_test/pos_test.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Analyze (python)
  • GitHub Check: Python CI

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
kairon/shared/pos/processor.py (2)

577-595: Critical: Add error handling when no matching config is found.

If no POS config matches the provided company_id, the variable config_id remains None, which will cause failures when creating a session (line 603) or building the order payload (line 651).

🐛 Proposed fix
         config_id = None
         for config in pos_configs:
             comp_id = config["company_id"][0] if config.get("company_id") else False
             if company_id == comp_id:
                 config_id = config["id"]
                 break
+        
+        if config_id is None:
+            raise AppException(f"No POS Config found for company_id {company_id}")

610-644: Fix inconsistent payment method ID extraction.

The payment method handling has a type inconsistency:

  • Line 614: payment_method_ids from an open session is a list of integers [1, 2, 3]
  • Lines 633-639: search_read returns a list of dictionaries [{"id": 1, ...}]
  • Line 644: payment_method_ids[0] works for the session case but fails for the search_read case (returns a dict, not an int)
🔧 Proposed fix
         if not payment_method_ids:
             payment_method_ids = self.jsonrpc_call(
                 session_id=session_id,
                 model="pos.payment.method",
                 method="search_read",
                 args=[[[]]],
                 kwargs={"limit": 1}
             )
+            # search_read returns list of dicts, extract IDs
+            if payment_method_ids:
+                payment_method_ids = [pm["id"] for pm in payment_method_ids]
 
         if not payment_method_ids:
             raise HTTPException(status_code=400, detail="No POS payment methods found")
 
         payment_method_id = payment_method_ids[0]
🤖 Fix all issues with AI agents
In @kairon/shared/pos/processor.py:
- Line 497: Update the create_pos_order function signature to mark partner_id as
optional per PEP 484 (e.g., partner_id: Optional[int] or partner_id: int | None)
and add the corresponding import (from typing import Optional) if using
Optional; keep the default = None unchanged and ensure any internal uses handle
None.

In @tests/integration_test/services_test.py:
- Line 1683: The test assigns a global value to pytest.session_id which creates
cross-test coupling; remove the shared assignment and instead expose the
session_id via a fixture (e.g., a session-scoped fixture named
session_id_fixture) that returns the session id to dependent tests, or pass the
session_id explicitly between helper calls; update any tests referencing
pytest.session_id to accept and use the fixture (or receive the value from the
helper) so tests remain isolated and order-independent while retaining the same
session value where needed.
🧹 Nitpick comments (4)
kairon/pos/odoo/odoo_pos.py (1)

50-57: Fix spacing for consistency.

The implementation is correct, but line 53 has inconsistent spacing compared to line 44.

📝 Minor style fix
-        company_id = kwargs.get("company_id",1)
+        company_id = kwargs.get("company_id", 1)
tests/integration_test/services_test.py (3)

1680-1680: Remove debug print statement.

The print(actual) statement should be removed as it's debug code that shouldn't be in production tests.

🧹 Proposed cleanup
     actual = response.json()
-    print(actual)
     assert actual["uid"] == 1

2350-2350: Remove debug print statement.

The print(data) statement should be removed as it's debug code.

🧹 Proposed cleanup
     data = response.json()
-    print(data)
 

2298-2356: Consider validating company_id in test assertions.

The mock response at line 2305 includes company_id: 2, which suggests company context is important for this flow. However, the test assertions don't verify that the company_id was properly handled or returned in the response. Consider adding an assertion to ensure the multi-tenant context is correctly propagated.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f8d329 and e55dde0.

📒 Files selected for processing (5)
  • kairon/api/app/routers/pos.py
  • kairon/pos/odoo/odoo_pos.py
  • kairon/shared/pos/models.py
  • kairon/shared/pos/processor.py
  • tests/integration_test/services_test.py
🧰 Additional context used
🧬 Code graph analysis (3)
kairon/api/app/routers/pos.py (2)
kairon/shared/pos/models.py (2)
  • POSOrderRequest (28-31)
  • BranchRequest (16-20)
kairon/pos/odoo/odoo_pos.py (1)
  • authenticate (24-39)
tests/integration_test/services_test.py (2)
kairon/shared/data/data_objects.py (1)
  • BotSettings (903-950)
kairon/shared/utils.py (1)
  • Utility (93-2369)
kairon/shared/pos/processor.py (2)
kairon/exceptions.py (1)
  • AppException (1-3)
kairon/api/app/routers/pos.py (1)
  • create_branch (48-60)
🪛 Ruff (0.14.10)
kairon/api/app/routers/pos.py

51-51: Unused function argument: current_user

(ARG001)


51-51: Do not perform function call Security in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

kairon/shared/pos/processor.py

497-497: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)


588-588: Avoid specifying long messages outside the exception class

(TRY003)


722-722: Local variable branch_data is assigned to but never used

Remove assignment to unused variable branch_data

(F841)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Analyze (python)
  • GitHub Check: Python CI
🔇 Additional comments (11)
kairon/shared/pos/models.py (3)

16-20: LGTM! Consider adding field validation.

The BranchRequest model is well-defined. However, note that the create_branch implementation in processor.py expects specific Indian state names. Consider adding a Pydantic validator or using an enum to restrict state to valid values.


14-14: LGTM!

The addition of company_id with a sensible default maintains backward compatibility while enabling multi-tenant support.


31-31: LGTM!

Consistent with the LoginRequest changes, enabling company-specific order routing.

kairon/pos/odoo/odoo_pos.py (2)

24-39: LGTM!

The authenticate method correctly extracts and propagates company_id to the appropriate list generation methods based on page_type.


41-48: LGTM!

The dynamic URL construction with cids={company_id} enables company-specific product listings.

kairon/api/app/routers/pos.py (4)

6-6: LGTM!

Import addition supports the new branch creation endpoint.


19-29: LGTM!

The company_id is correctly propagated from the request to the POS authentication flow.


47-60: LGTM!

The new branch creation endpoint follows established patterns in this module. The static analysis warnings about current_user and Security are false positives—this is the standard FastAPI dependency injection pattern for authentication.


105-114: LGTM!

The company_id is correctly propagated to the order creation flow, enabling multi-tenant order handling.

tests/integration_test/services_test.py (2)

1687-1726: LGTM! Well-structured branch creation test.

The test properly mocks the POSProcessor.create_branch method, verifies the response structure, and asserts the method was called with the correct parameters.


4165-4170: LGTM! Good backward compatibility test update.

Properly updates the existing test to reflect the new company_id parameter in the authenticate method signature, ensuring backward compatibility with the default value.

)[0]

def create_pos_order(self, session_id: str, products: list, partner_id: int = None):
def create_pos_order(self, session_id: str, products: list, partner_id: int = None, company_id: int = 1):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix type hint for optional parameter.

The partner_id parameter should be explicitly typed as optional per PEP 484.

🔧 Suggested fix
-    def create_pos_order(self, session_id: str, products: list, partner_id: int = None, company_id: int = 1):
+    def create_pos_order(self, session_id: str, products: list, partner_id: int | None = None, company_id: int = 1):
🧰 Tools
🪛 Ruff (0.14.10)

497-497: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)

🤖 Prompt for AI Agents
In @kairon/shared/pos/processor.py at line 497, Update the create_pos_order
function signature to mark partner_id as optional per PEP 484 (e.g., partner_id:
Optional[int] or partner_id: int | None) and add the corresponding import (from
typing import Optional) if using Optional; keep the default = None unchanged and
ensure any internal uses handle None.

Comment on lines 682 to 741
def create_branch(self, session_id: str, branch_name: str, street: str, city: str, state: str):
INDIA_STATE_MAP = {
"Andaman and Nicobar": 577,
"Andhra Pradesh": 578,
"Arunachal Pradesh": 579,
"Assam": 580,
"Bihar": 581,
"Chattisgarh": 583,
"Chandigarh": 582,
"Daman and Diu": 585,
"Delhi": 586,
"Dadra and Nagar Haveli": 584,
"Goa": 587,
"Gujarat": 588,
"Himachal Pradesh": 590,
"Haryana": 589,
"Jharkhand": 592,
"Jammu and Kashmir": 591,
"Karnataka": 593,
"Kerala": 594,
"Lakshadweep": 595,
"Maharashtra": 597,
"Meghalaya": 599,
"Manipur": 598,
"Madhya Pradesh": 596,
"Mizoram": 600,
"Nagaland": 601,
"Odisha": 602,
"Punjab": 604,
"Puducherry": 603,
"Rajasthan": 605,
"Sikkim": 606,
"Tamil Nadu": 607,
"Tripura": 609,
"Telangana": 608,
"Uttarakhand": 611,
"Uttar Pradesh": 610,
"West Bengal": 612
}
state_id=INDIA_STATE_MAP[state]
branch_data = self.jsonrpc_call(
session_id=session_id,
model="res.company",
method="create",
args= [
{
"name": branch_name,
"parent_id": 1,
"currency_id": 1,
"country_id": 104,
"state_id": state_id,
"street": street,
"city": city,
"active": True
}
],
kwargs= {}
)

return {"status": "created"}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add validation and error handling for branch creation.

The create_branch method has several issues:

  1. Line 721: Will raise KeyError if an invalid state name is provided
  2. Lines 683-720: The large state mapping dictionary should be a class/module-level constant, not defined inside the method
  3. Line 722: The branch_data result is never used—should either return it or verify the creation succeeded
  4. Hardcoded values: parent_id=1, currency_id=1, country_id=104 are hardcoded without documentation
  5. Geographic limitation: Method only supports Indian states without documenting this constraint
♻️ Proposed improvements

Move the state map to class level and add validation:

+class POSProcessor:
+    
+    INDIA_STATE_MAP = {
+        "Andaman and Nicobar": 577,
+        # ... rest of states ...
+    }
+    
     def create_branch(self, session_id: str, branch_name: str, street: str, city: str, state: str):
-        INDIA_STATE_MAP = {
-            "Andaman and Nicobar": 577,
-            # ... all states ...
-        }
-        state_id=INDIA_STATE_MAP[state]
+        """
+        Create a new branch (res.company) in Odoo.
+        Note: Currently supports Indian states only.
+        """
+        if state not in self.INDIA_STATE_MAP:
+            raise AppException(f"Invalid state '{state}'. Must be a valid Indian state.")
+        
+        state_id = self.INDIA_STATE_MAP[state]
+        
         branch_data = self.jsonrpc_call(
             session_id=session_id,
             model="res.company",
             method="create",
             args= [
                 {
                     "name": branch_name,
                     "parent_id": 1,
                     "currency_id": 1,
                     "country_id": 104,
                     "state_id": state_id,
                     "street": street,
                     "city": city,
                     "active": True
                 }
             ],
             kwargs= {}
         )
 
-        return {"status": "created"}
+        return {"status": "created", "branch_id": branch_data}

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Ruff (0.14.10)

722-722: Local variable branch_data is assigned to but never used

Remove assignment to unused variable branch_data

(F841)

print(actual)
assert actual["uid"] == 1
assert actual["session_id"] == "fake-session-id-123"
pytest.session_id = actual["session_id"]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Test isolation concern: shared session state.

Setting pytest.session_id creates a dependency between tests. If tests run in a different order or isolation, this could cause failures. Consider using fixtures or ensuring tests can run independently.

🤖 Prompt for AI Agents
In @tests/integration_test/services_test.py at line 1683, The test assigns a
global value to pytest.session_id which creates cross-test coupling; remove the
shared assignment and instead expose the session_id via a fixture (e.g., a
session-scoped fixture named session_id_fixture) that returns the session id to
dependent tests, or pass the session_id explicitly between helper calls; update
any tests referencing pytest.session_id to accept and use the fixture (or
receive the value from the helper) so tests remain isolated and
order-independent while retaining the same session value where needed.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
kairon/shared/pos/processor.py (1)

631-665: Type mismatch in payment_method_id extraction logic.

There's inconsistent handling of payment_method_ids:

  • Line 635: Extracts from session as a list of IDs (integers)
  • Lines 653-660: Falls back to search_read which returns a list of dictionaries
  • Line 665: Assumes payment_method_ids[0] is the ID directly

When payment_method_ids comes from the session, it's a list of integers. When it comes from search_read, it's a list of dicts with an id key. This will cause incorrect behavior.

Proposed fix
         if not payment_method_ids:
-            payment_method_ids = self.jsonrpc_call(
+            payment_methods = self.jsonrpc_call(
                 session_id=session_id,
                 model="pos.payment.method",
                 method="search_read",
                 args=[[[]]],
-                kwargs={"limit": 1}
+                kwargs={"fields": ["id"], "limit": 1}
             )
+            if payment_methods:
+                payment_method_ids = [pm["id"] for pm in payment_methods]

         if not payment_method_ids:
             raise HTTPException(status_code=400, detail="No POS payment methods found")

         payment_method_id = payment_method_ids[0]
🤖 Fix all issues with AI agents
In @kairon/shared/pos/processor.py:
- Around line 611-616: The loop over pos_configs can leave config_id as None
when no entry matches company_id; after the loop (before any use like the
subsequent domain filter), add an explicit check that raises a clear exception
(e.g., ValueError or a domain-specific exception) if config_id is None,
including the company_id in the message so callers know which company had no POS
config; ensure this check is placed right after the for-loop that sets config_id
and before any code that uses config_id.
- Around line 754-756: Replace the hardcoded parent_id: 1, currency_id: 1, and
country_id: 104 with values read from the existing POS Odoo config object
(Utility.environment["pos"]["odoo"]); update the payload construction in the pos
processor where these keys are set to use
Utility.environment["pos"]["odoo"]["parent_id"], ["currency_id"], and
["country_id"] (or sensible defaults if keys are missing) so different
deployments can override them via config rather than using fixed IDs.
🧹 Nitpick comments (10)
tests/integration_test/services_test.py (3)

1662-1685: Remove @pytest.mark.asyncio decorator and debug print statement.

The function is synchronous (def), so @pytest.mark.asyncio is unnecessary and misleading. Also, the print(actual) on line 1681 appears to be a debug artifact.

🧹 Suggested cleanup
-@pytest.mark.asyncio
 @responses.activate
 def test_pos_login_with_cid():
     bot_settings = BotSettings.objects(bot=pytest.bot).get()
     bot_settings.pos_enabled = True
     bot_settings.save()

     base = Utility.environment["pos"]["odoo"]["odoo_url"]
     auth = f"{base}/web/session/authenticate"

     responses.add(responses.POST, auth, json={"result": {"uid": 1}},
                   headers={"Set-Cookie": "session_id=fake-session-id-123; Path=/;"}, status=200)

     response = client.post(
         f"/api/bot/{pytest.bot}/pos/odoo/login",
         json={"client_name": "Test Client", "company_id": 2},
         headers={"Authorization": pytest.token_type + " " + pytest.access_token},
     )
     actual = response.json()
-    print(actual)
     assert actual["uid"] == 1
     assert actual["session_id"] == "fake-session-id-123"
     pytest.session_id = actual["session_id"]
     assert actual["url"] == 'http://localhost:8080/web#action=388&model=product.template&view_type=kanban&cids=2&menu_id=233'

1731-1756: Consider adding status code assertion.

The test mocks an HTTPException with status_code=404, but only verifies the error message. Adding a status code assertion would strengthen the test by confirming the HTTP status is propagated correctly.

✅ Suggested improvement
         actual = response.json()
+        assert response.status_code == 404
         assert actual["message"] == "Error in creating branch"

2319-2386: Remove decorator, debug print, and consider test isolation.

Three issues:

  1. @pytest.mark.asyncio is unnecessary for this synchronous function.
  2. print(data) on line 2380 is a debug artifact.
  3. This test relies on pytest.session_id set by test_pos_login_with_cid. If tests run in isolation or a different order, this test will fail. Consider setting up the session_id locally or using a fixture.
🧹 Suggested cleanup for decorator and print
-@pytest.mark.asyncio
 @responses.activate
 def test_create_pos_order_success_with_cid():
     base = Utility.environment["pos"]["odoo"]["odoo_url"]
     url = f"{base}/web/dataset/call_kw"
     # ... mocks ...

     response = client.post(
         f"/api/bot/{pytest.bot}/pos/odoo/pos_order?session_id={pytest.session_id}",
         json=payload,
         headers={"Authorization": pytest.token_type + " " + pytest.access_token},
     )

     data = response.json()
-    print(data)

     assert data["success"]
kairon/shared/pos/data_objects.py (1)

22-22: Consider adding schema documentation or validation for the branches field.

The branches field accepts any dictionary structure. While this provides flexibility, it may lead to inconsistent data. Based on the save_branch_details method in the processor, expected keys are branch_name and company_id. Consider documenting the expected schema or using an EmbeddedDocument for type safety.

tests/unit_test/pos_test.py (1)

131-143: Consider asserting the specific status code and message for invalid state.

The test verifies that an HTTPException is raised but doesn't validate the status code (expected 400) or error message. This could mask regressions if the error handling changes.

Proposed improvement
 def test_create_branch_invalid_state():
     service = POSProcessor()

-    with pytest.raises(HTTPException):
+    with pytest.raises(HTTPException) as exc:
         service.create_branch(
             session_id="session123",
             branch_name="Unknown Branch",
             street="Some Street",
             city="Some City",
             state="InvalidState",
             bot="test_bot",
             user="test_user"
         )
+
+    assert exc.value.status_code == 400
+    assert "Invalid state" in exc.value.detail
kairon/api/app/routers/pos.py (1)

47-62: Fix inconsistent spacing in keyword arguments.

Lines 59-60 use bot = and user = with spaces around =, which is inconsistent with PEP 8 style for keyword arguments and with the rest of the codebase.

Proposed fix
     result = pos_processor.create_branch(
         session_id=session_id,
         branch_name=req.branch_name,
         street=req.street,
         city=req.city,
         state=req.state,
-        bot = current_user.get_bot(),
-        user = current_user.get_user()
+        bot=current_user.get_bot(),
+        user=current_user.get_user()
     )
kairon/shared/pos/processor.py (4)

139-158: Remove unused parameters user and pos_type.

The user and pos_type parameters are declared but never used in the method body. Either remove them or implement the intended functionality (e.g., logging/auditing with user).

Proposed fix
 @staticmethod
-def save_branch_details(bot: str, branch_name: str, company_id: int, user: str, pos_type: POSType = POSType.odoo.value):
+def save_branch_details(bot: str, branch_name: str, company_id: int):
     record = POSClientDetails.objects(bot=bot, pos_type=POSType.odoo.value).first()
     if not record:
         raise AppException("No POS client configuration found for this bot.")

Then update the caller at line 768:

-POSProcessor.save_branch_details(bot, branch_name, branch_data, user)
+POSProcessor.save_branch_details(bot, branch_name, branch_data)

742-745: Use raise ... from to preserve exception context.

Per B904, when re-raising an exception within an except block, use raise ... from err or raise ... from None to properly chain exceptions and preserve the traceback.

Proposed fix
         try:
             state_id = INDIA_STATE_MAP[state]
         except KeyError:
-            raise HTTPException(status_code=400, detail=f"Invalid state: {state}")
+            raise HTTPException(status_code=400, detail=f"Invalid state: {state}") from None

518-518: Use explicit Optional type hint instead of implicit None default.

PEP 484 recommends using explicit Optional[int] or int | None when a parameter can be None.

Proposed fix
-def create_pos_order(self, session_id: str, products: list, partner_id: int = None, company_id: int = 1):
+def create_pos_order(self, session_id: str, products: list, partner_id: int | None = None, company_id: int = 1):

703-741: Consider moving INDIA_STATE_MAP to a constants file.

The state map is defined inside the method, making it harder to maintain and test. Moving it to kairon/shared/pos/constants.py would improve maintainability and allow reuse.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e55dde0 and ac0c3ff.

📒 Files selected for processing (5)
  • kairon/api/app/routers/pos.py
  • kairon/shared/pos/data_objects.py
  • kairon/shared/pos/processor.py
  • tests/integration_test/services_test.py
  • tests/unit_test/pos_test.py
🧰 Additional context used
🧬 Code graph analysis (3)
kairon/api/app/routers/pos.py (3)
kairon/shared/pos/models.py (4)
  • LoginRequest (11-14)
  • ClientRequest (7-8)
  • POSOrderRequest (28-31)
  • BranchRequest (16-20)
kairon/pos/odoo/odoo_pos.py (1)
  • authenticate (24-39)
kairon/shared/pos/processor.py (1)
  • create_branch (703-769)
kairon/shared/pos/processor.py (4)
kairon/shared/pos/constants.py (1)
  • POSType (4-5)
kairon/shared/pos/data_objects.py (1)
  • POSClientDetails (13-37)
kairon/exceptions.py (1)
  • AppException (1-3)
kairon/api/app/routers/pos.py (1)
  • create_branch (48-62)
tests/integration_test/services_test.py (1)
kairon/shared/data/data_objects.py (1)
  • BotSettings (903-950)
🪛 Ruff (0.14.10)
kairon/api/app/routers/pos.py

51-51: Do not perform function call Security in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)

kairon/shared/pos/processor.py

140-140: Unused static method argument: user

(ARG004)


140-140: Unused static method argument: pos_type

(ARG004)


143-143: Avoid specifying long messages outside the exception class

(TRY003)


518-518: PEP 484 prohibits implicit Optional

Convert to T | None

(RUF013)


609-609: Avoid specifying long messages outside the exception class

(TRY003)


745-745: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Codacy Static Code Analysis
  • GitHub Check: Analyze (python)
  • GitHub Check: Python CI
🔇 Additional comments (7)
tests/integration_test/services_test.py (3)

102-102: LGTM!

The HTTPException import is appropriately added to support mocking HTTP errors in the new failure test case.


1688-1729: LGTM!

The test properly mocks the processor, validates the response structure, and verifies that the correct parameters are passed to the create_branch method.


4195-4200: LGTM!

The assertion is correctly updated to verify that company_id=1 is passed to the authenticate method, consistent with the PR's objective of propagating company_id through POS flows.

tests/unit_test/pos_test.py (2)

75-105: LGTM! Good test coverage for the success path.

The test correctly mocks both jsonrpc_call and save_branch_details, and validates the return payload and method invocations.


107-129: LGTM! Proper error handling verification.

The test correctly validates that a 404 HTTPException is raised when jsonrpc_call returns None, and ensures save_branch_details is not called on failure.

kairon/api/app/routers/pos.py (2)

27-27: LGTM!

The company_id propagation to the authenticate call aligns with the multi-tenant branch support.


113-114: LGTM!

The company_id is correctly propagated to create_pos_order for multi-tenant order creation.

Comment on lines +611 to +616
config_id = None
for config in pos_configs:
comp_id = config["company_id"][0] if config.get("company_id") else False
if company_id == comp_id:
config_id = config["id"]
break
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle the case when no matching POS config is found for the company_id.

If no config matches the provided company_id, config_id remains None, which will cause issues in subsequent operations (e.g., line 624 where it's used in a domain filter). This should raise an explicit error.

Proposed fix
         config_id = None
         for config in pos_configs:
             comp_id = config["company_id"][0] if config.get("company_id") else False
             if company_id == comp_id:
                 config_id = config["id"]
                 break
+
+        if config_id is None:
+            raise HTTPException(
+                status_code=404,
+                detail=f"No POS config found for company_id {company_id}"
+            )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
config_id = None
for config in pos_configs:
comp_id = config["company_id"][0] if config.get("company_id") else False
if company_id == comp_id:
config_id = config["id"]
break
config_id = None
for config in pos_configs:
comp_id = config["company_id"][0] if config.get("company_id") else False
if company_id == comp_id:
config_id = config["id"]
break
if config_id is None:
raise HTTPException(
status_code=404,
detail=f"No POS config found for company_id {company_id}"
)
🤖 Prompt for AI Agents
In @kairon/shared/pos/processor.py around lines 611 - 616, The loop over
pos_configs can leave config_id as None when no entry matches company_id; after
the loop (before any use like the subsequent domain filter), add an explicit
check that raises a clear exception (e.g., ValueError or a domain-specific
exception) if config_id is None, including the company_id in the message so
callers know which company had no POS config; ensure this check is placed right
after the for-loop that sets config_id and before any code that uses config_id.

Comment on lines +754 to +756
"parent_id": 1,
"currency_id": 1,
"country_id": 104,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, examine the specific file and lines in question
cd /repo && cat -n kairon/shared/pos/processor.py | sed -n '750,760p'

Repository: digiteinfotech/kairon

Length of output: 120


🏁 Script executed:

# Search for usage patterns of these IDs across the codebase
rg -n "parent_id|currency_id|country_id" --type py -B2 -A2

Repository: digiteinfotech/kairon

Length of output: 2602


🏁 Script executed:

# Check if there's any configuration file or module for Odoo settings
fd -e py -e json -e yaml -e toml -e conf | xargs rg -l "parent_id|currency_id|country_id|odoo.*config" 2>/dev/null | head -20

Repository: digiteinfotech/kairon

Length of output: 489


🏁 Script executed:

cat -n kairon/shared/pos/processor.py | sed -n '740,770p'

Repository: digiteinfotech/kairon

Length of output: 1290


🏁 Script executed:

# Search for the function containing these lines and understand the context
rg -n "def.*\(" kairon/shared/pos/processor.py | head -20

Repository: digiteinfotech/kairon

Length of output: 1651


🏁 Script executed:

# Check if there's any Odoo configuration or settings file
rg -n "odoo|currency_id|country_id" kairon/shared/pos/processor.py -B5 -A5

Repository: digiteinfotech/kairon

Length of output: 4795


🏁 Script executed:

# Look for configuration files that might contain these ID mappings
fd -type f \( -name "*.py" -o -name "*.json" -o -name "*.yaml" -o -name "*.toml" -o -name "*.conf" \) | xargs rg -l "config|setting" | grep -E "(config|setting|constant|env)" | head -10

Repository: digiteinfotech/kairon

Length of output: 852


🏁 Script executed:

# Look for configuration files and environment setup
rg -n "parent_id|currency_id|country_id|odoo.*config" kairon/ --type py | grep -v test | grep -v ".pyc"

Repository: digiteinfotech/kairon

Length of output: 478


🏁 Script executed:

# Check the Utility.environment setup to understand config structure
rg -n "environment\[.*pos" kairon/ --type py | head -20

Repository: digiteinfotech/kairon

Length of output: 480


🏁 Script executed:

# Look for any dynamic ID resolution or query patterns for these entities
rg -n "search.*parent|search.*currency|search.*country" kairon/shared/pos/ --type py -B2 -A2

Repository: digiteinfotech/kairon

Length of output: 47


🏁 Script executed:

# Check if there are similar hardcoded patterns elsewhere in the codebase
rg -n "jsonrpc_call.*create" kairon/shared/pos/processor.py --type py -B3 -A3

Repository: digiteinfotech/kairon

Length of output: 458


🏁 Script executed:

# Check if there's any documentation or comments about India-specificity
rg -n "india|104|country_id" kairon/shared/pos/processor.py -B5 -A5 | head -50

Repository: digiteinfotech/kairon

Length of output: 467


🏁 Script executed:

# Check the create_branch function signature and docstring
sed -n '703,720p' kairon/shared/pos/processor.py

Repository: digiteinfotech/kairon

Length of output: 729


🏁 Script executed:

# Search for any test files that might show intended usage
rg -n "create_branch|country_id.*104" tests/ --type py

Repository: digiteinfotech/kairon

Length of output: 1195


🏁 Script executed:

# Check Utility.environment structure to see how config is loaded
rg -n "class Utility" kairon/shared/utils.py -A 30 | head -50

Repository: digiteinfotech/kairon

Length of output: 1210


🏁 Script executed:

# Look for environment configuration files
fd -type f -name "*.yaml" -o -name "*.yml" -o -name "*.json" | xargs rg -l "pos.*odoo\|country\|currency" 2>/dev/null | head -10

Repository: digiteinfotech/kairon

Length of output: 235


🏁 Script executed:

# Check if the IDs are mentioned in documentation or readme
find . -name "README*" -o -name "*.md" | xargs rg -l "104\|country_id\|create_branch" 2>/dev/null

Repository: digiteinfotech/kairon

Length of output: 49


🏁 Script executed:

# Look at the full create_branch function to understand the context better
sed -n '703,770p' kairon/shared/pos/processor.py

Repository: digiteinfotech/kairon

Length of output: 2306


🏁 Script executed:

# Check if there's any configuration that might override these values
rg -n "parent_id.*=|currency_id.*=|country_id.*=" kairon/ --type py | grep -v test | grep -v ".pyc"

Repository: digiteinfotech/kairon

Length of output: 183


🏁 Script executed:

# Look for Odoo documentation or comments about these standard IDs
rg -n "INR|india|odoo" kairon/shared/pos/processor.py -i

Repository: digiteinfotech/kairon

Length of output: 1795


Make country_id, currency_id, and parent_id configurable instead of hardcoded.

The hardcoded values parent_id: 1, currency_id: 1, and country_id: 104 are specific to a particular Odoo instance setup. Different deployments may have different ID assignments for the parent company, default currency, and country. Since the codebase already uses Utility.environment["pos"]["odoo"] for configuration (odoo_url, odoo_domain, odoo_master_password), these IDs should be moved to the same configuration mechanism rather than hardcoded. Without this, the code will fail or create incorrect associations when deployed to Odoo instances with different configurations.

🤖 Prompt for AI Agents
In @kairon/shared/pos/processor.py around lines 754 - 756, Replace the hardcoded
parent_id: 1, currency_id: 1, and country_id: 104 with values read from the
existing POS Odoo config object (Utility.environment["pos"]["odoo"]); update the
payload construction in the pos processor where these keys are set to use
Utility.environment["pos"]["odoo"]["parent_id"], ["currency_id"], and
["country_id"] (or sensible defaults if keys are missing) so different
deployments can override them via config rather than using fixed IDs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant