-
Notifications
You must be signed in to change notification settings - Fork 84
odoo fixes #2295
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
odoo fixes #2295
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -357,9 +357,9 @@ def jsonrpc_search_read(self, session_id: str, model: str, domain: List[list], f | |
|
|
||
| return self.jsonrpc_call(session_id, model, "search_read", args=[domain], kwargs=kwargs) | ||
|
|
||
| def get_pos_products(self, session_id: str): | ||
| def get_pos_products(self, session_id: str, return_all: bool = True): | ||
| try: | ||
| domain = [["available_in_pos", "=", True]] | ||
| domain = [] if return_all else [["available_in_pos", "=", True]] | ||
|
|
||
| products = self.jsonrpc_call( | ||
| session_id=session_id, | ||
|
|
@@ -378,6 +378,21 @@ def get_pos_products(self, session_id: str): | |
| except Exception as e: | ||
| raise HTTPException(500, detail=f"Odoo error: {e}") | ||
|
|
||
| def invalidate_session(self, session_id): | ||
| url = f"{self.__base_url}/web/session/destroy" | ||
| cookies = {"session_id": session_id} | ||
| payload = { | ||
| "jsonrpc": "2.0", | ||
| "method": "call", | ||
| "params": {} | ||
| } | ||
| try: | ||
| response = requests.post(url, json=payload, cookies=cookies, timeout=30).json() | ||
| return response | ||
| except Exception as e: | ||
| raise HTTPException(400, detail=f"Odoo error: {e}") | ||
|
|
||
|
|
||
| def toggle_product_in_pos(self, session_id: str, product_id: int) -> Dict[str, Any]: | ||
| """ | ||
| Toggle product.template.available_in_pos boolean using session. | ||
|
|
@@ -501,19 +516,34 @@ def create_pos_order(self, session_id: str, products: list, partner_id: int = No | |
| """ | ||
|
|
||
| if not partner_id: | ||
| partner_id = self.jsonrpc_call( | ||
| existing = self.jsonrpc_call( | ||
| session_id=session_id, | ||
| model="res.partner", | ||
| method="create", | ||
| args=[{"name": "POS Customer", "customer_rank": 1}] | ||
| method="search_read", | ||
| args=[[["name", "=", "POS Customer"]]], | ||
| kwargs={"fields": ["id"], "limit": 1} | ||
| ) | ||
|
|
||
| if existing: | ||
| partner_id = existing[0]["id"] | ||
| else: | ||
| partner_id = self.jsonrpc_call( | ||
| session_id=session_id, | ||
| model="res.partner", | ||
| method="create", | ||
| args=[{ | ||
| "name": "POS Customer", | ||
| "customer_rank": 1 | ||
| }] | ||
| ) | ||
|
Comment on lines
+519
to
+538
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Potential race condition in partner creation. When This is unlikely to occur frequently and would only result in duplicate partners (not data corruption), but it's worth noting. To eliminate the race condition, consider using Odoo's 🤖 Prompt for AI Agents |
||
|
|
||
| order_lines = [] | ||
| total = 0.0 | ||
|
|
||
| for p in products: | ||
| product_id = p["product_id"] | ||
| qty = p["qty"] | ||
| discount = p.get("discount", 0) | ||
|
|
||
|
Comment on lines
+546
to
547
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add validation for discount range. The discount value is extracted from the product data (line 542) and applied to pricing calculations (line 583), but there's no validation to ensure the discount is within a reasonable range (e.g., 0-100). Invalid discount values (negative or > 100) could result in incorrect pricing, including negative subtotals or prices exceeding the original amount. Add validation after extracting the discount: for p in products:
product_id = p["product_id"]
qty = p["qty"]
discount = p.get("discount", 0)
+
+ if discount < 0 or discount > 100:
+ raise HTTPException(
+ status_code=400,
+ detail=f"Discount must be between 0 and 100, got {discount}"
+ )Also applies to: 583-600 🤖 Prompt for AI Agents |
||
| product_data = self.jsonrpc_call( | ||
| session_id=session_id, | ||
|
|
@@ -554,7 +584,7 @@ def create_pos_order(self, session_id: str, products: list, partner_id: int = No | |
| if t["amount_type"] == "percent": | ||
| tax_total += (price_unit * qty) * (t["amount"] / 100) | ||
|
|
||
| subtotal_excl = price_unit * qty | ||
| subtotal_excl = (price_unit * qty) * (1 - (discount / 100)) | ||
| subtotal_incl = subtotal_excl + tax_total | ||
|
|
||
| total += subtotal_incl | ||
|
|
@@ -571,7 +601,7 @@ def create_pos_order(self, session_id: str, products: list, partner_id: int = No | |
|
|
||
| "price_subtotal": subtotal_excl, | ||
| "price_subtotal_incl": subtotal_incl, | ||
| "discount": 0, | ||
| "discount": discount, | ||
| }]) | ||
|
|
||
| pos_configs = self.jsonrpc_call( | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1932,6 +1932,8 @@ def test_create_pos_order_product_not_found(): | |
| base = Utility.environment["pos"]["odoo"]["odoo_url"] | ||
| url = f"{base}/web/dataset/call_kw" | ||
|
|
||
| responses.add(responses.POST, url, json={"result": []}, status=200) | ||
|
|
||
| responses.add(responses.POST, url, json={"result": 99}, status=200) | ||
|
|
||
| responses.add(responses.POST, url, json={"result": []}, status=200) | ||
|
|
@@ -1962,6 +1964,8 @@ def test_create_pos_order_product_not_available(): | |
| base = Utility.environment["pos"]["odoo"]["odoo_url"] | ||
| url = f"{base}/web/dataset/call_kw" | ||
|
|
||
| responses.add(responses.POST, url, json={"result": []}, status=200) | ||
|
|
||
| responses.add(responses.POST, url, json={"result": 98}, status=200) | ||
|
|
||
| responses.add( | ||
|
|
@@ -2002,6 +2006,8 @@ def test_create_pos_order_no_pos_config(): | |
| base = Utility.environment["pos"]["odoo"]["odoo_url"] | ||
| url = f"{base}/web/dataset/call_kw" | ||
|
|
||
| responses.add(responses.POST, url, json={"result": []}, status=200) | ||
|
|
||
| responses.add(responses.POST, url, json={"result": 99}, status=200) | ||
|
|
||
| responses.add( | ||
|
|
@@ -2044,6 +2050,8 @@ def test_create_pos_order_no_payment_method(): | |
| base = Utility.environment["pos"]["odoo"]["odoo_url"] | ||
| url = f"{base}/web/dataset/call_kw" | ||
|
|
||
| responses.add(responses.POST, url, json={"result": []}, status=200) | ||
|
|
||
| responses.add(responses.POST, url, json={"result": 99}, status=200) | ||
|
|
||
| responses.add( | ||
|
|
@@ -2112,6 +2120,8 @@ def test_create_pos_order_without_partner(): | |
| base = Utility.environment["pos"]["odoo"]["odoo_url"] | ||
| url = f"{base}/web/dataset/call_kw" | ||
|
|
||
| responses.add(responses.POST, url, json={"result": []}, status=200) | ||
|
|
||
| responses.add(responses.POST, url, json={"result": 500}, status=200) | ||
|
|
||
| responses.add( | ||
|
|
@@ -2625,6 +2635,93 @@ def test_get_pos_products_success(): | |
| assert not data["message"] | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| @responses.activate | ||
| def test_invalidate_session_success(): | ||
| base = Utility.environment["pos"]["odoo"]["odoo_url"] | ||
| url = f"{base}/web/session/destroy" | ||
|
|
||
| responses.add( | ||
| responses.POST, | ||
| url, | ||
| json={"result": True}, | ||
| status=200 | ||
| ) | ||
|
|
||
| response = client.post( | ||
| f"/api/bot/{pytest.bot}/pos/odoo/invalidate/session?session_id={pytest.session_id}", | ||
| headers={"Authorization": pytest.token_type + " " + pytest.access_token}, | ||
| ) | ||
|
|
||
| data = response.json() | ||
| print(data) | ||
|
|
||
| assert data["success"] is True | ||
| assert data["message"] == "Session invalidated successfully" | ||
| assert not data["data"] | ||
| assert data["error_code"] == 0 | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| @responses.activate | ||
| def test_invalidate_session_odoo_error_response(): | ||
| base = Utility.environment["pos"]["odoo"]["odoo_url"] | ||
| url = f"{base}/web/session/destroy" | ||
|
|
||
| responses.add( | ||
| responses.POST, | ||
| url, | ||
| json={ | ||
| "error": { | ||
| "code": 200, | ||
| "message": "Session expired" | ||
| } | ||
| }, | ||
| status=200 | ||
| ) | ||
|
|
||
| response = client.post( | ||
| f"/api/bot/{pytest.bot}/pos/odoo/invalidate/session?session_id=invalid-session", | ||
| headers={"Authorization": pytest.token_type + " " + pytest.access_token}, | ||
| ) | ||
|
|
||
| data = response.json() | ||
| print(data) | ||
|
|
||
| assert data["success"] is True | ||
| assert data["error_code"] == 0 | ||
| assert not data["data"] | ||
| assert data["message"] == "Session invalidated successfully" | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| @responses.activate | ||
| def test_invalidate_session_request_failure(): | ||
| import requests | ||
| base = Utility.environment["pos"]["odoo"]["odoo_url"] | ||
| url = f"{base}/web/session/destroy" | ||
|
|
||
| responses.add( | ||
| responses.POST, | ||
| url, | ||
| body=requests.exceptions.ConnectionError("Connection refused"), | ||
| status=400 | ||
| ) | ||
|
|
||
| response = client.post( | ||
| f"/api/bot/{pytest.bot}/pos/odoo/invalidate/session?session_id={pytest.session_id}", | ||
| headers={"Authorization": pytest.token_type + " " + pytest.access_token}, | ||
| ) | ||
|
|
||
| data = response.json() | ||
| print(data) | ||
|
|
||
| assert not data["success"] | ||
| assert "Connection refused" in data["message"] | ||
| assert not data["data"] | ||
| assert data["error_code"] == 400 | ||
|
|
||
|
Comment on lines
+2697
to
+2723
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Move import to module level and remove debug print statement. Line 2700 imports Additionally, remove the debug print statement at line 2717. Apply this diff: @pytest.mark.asyncio
@responses.activate
def test_invalidate_session_request_failure():
- import requests
base = Utility.environment["pos"]["odoo"]["odoo_url"]
url = f"{base}/web/session/destroy"
responses.add(
responses.POST,
url,
body=requests.exceptions.ConnectionError("Connection refused"),
status=400
)
response = client.post(
f"/api/bot/{pytest.bot}/pos/odoo/invalidate/session?session_id={pytest.session_id}",
headers={"Authorization": pytest.token_type + " " + pytest.access_token},
)
data = response.json()
- print(data)
assert not data["success"]Note: Ensure
🤖 Prompt for AI Agents |
||
|
|
||
| def test_secure_collection_crud_lifecycle(): | ||
| # Step 1: Add a bot | ||
| add_bot_resp = client.post( | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Default value mismatch between router and processor.
The router defines
return_allwithdefault=False, but the processor'sget_pos_productsmethod hasreturn_all: bool = Trueas its default. While this doesn't cause a runtime issue (since the router always passes the parameter), the inconsistency can lead to confusion.Consider aligning the defaults:
Or update the processor to match:
📝 Committable suggestion
🧰 Tools
🪛 Ruff (0.14.8)
126-126: Unused function argument:
current_user(ARG001)
126-126: Do not perform function call
Securityin argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable(B008)
🤖 Prompt for AI Agents