diff --git a/shopinvader_api_sale/routers/__init__.py b/shopinvader_api_sale/routers/__init__.py index 7b95576c52..20c121efbd 100644 --- a/shopinvader_api_sale/routers/__init__.py +++ b/shopinvader_api_sale/routers/__init__.py @@ -1 +1,2 @@ from .sales import sale_router +from .sale_lines import sale_line_router diff --git a/shopinvader_api_sale/routers/sale_lines.py b/shopinvader_api_sale/routers/sale_lines.py new file mode 100644 index 0000000000..5e6f91b806 --- /dev/null +++ b/shopinvader_api_sale/routers/sale_lines.py @@ -0,0 +1,86 @@ +# Copyright 2024 Akretion (http://www.akretion.com). +# @author Florian Mounier +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from typing import Annotated + +from fastapi import APIRouter, Depends + +from odoo import api, fields, models + +from odoo.addons.base.models.res_partner import Partner as ResPartner +from odoo.addons.extendable_fastapi.schemas import PagedCollection +from odoo.addons.fastapi.dependencies import ( + authenticated_partner, + authenticated_partner_env, + paging, +) +from odoo.addons.fastapi.schemas import Paging +from odoo.addons.sale.models.sale_order_line import SaleOrderLine +from odoo.addons.shopinvader_filtered_model.utils import FilteredModelAdapter + +from ..schemas import SaleLineSearch, SaleLineWithSale + +sale_line_router = APIRouter(tags=["sales"]) + + +@sale_line_router.get("/sale_lines") +def search( + params: Annotated[SaleLineSearch, Depends()], + paging: Annotated[Paging, Depends(paging)], + env: Annotated[api.Environment, Depends(authenticated_partner_env)], + partner: Annotated[ResPartner, Depends(authenticated_partner)], +) -> PagedCollection[SaleLineWithSale]: + """Get / search sale order lines. The list contains only sale order lines from the + authenticated user""" + count, sols = ( + env["shopinvader_api_sale.sale_line_router.helper"] + .new({"partner": partner}) + ._search(paging, params) + ) + return PagedCollection[SaleLineWithSale]( + count=count, + items=[SaleLineWithSale.from_sale_order_line(sol) for sol in sols], + ) + + +@sale_line_router.get("/sale_lines/{sale_line_id}") +def get( + sale_line_id: int, + env: Annotated[api.Environment, Depends(authenticated_partner_env)], + partner: Annotated[ResPartner, Depends(authenticated_partner)], +) -> SaleLineWithSale: + """ + Get sale order of authenticated user with specific sale_id + """ + return SaleLineWithSale.from_sale_order_line( + env["shopinvader_api_sale.sale_line_router.helper"] + .new({"partner": partner}) + ._get(sale_line_id) + ) + + +class ShopinvaderApiSaleSaleLineRouterHelper(models.AbstractModel): + _name = "shopinvader_api_sale.sale_line_router.helper" + _description = "Shopinvader Api Sale Line Service Helper" + + partner = fields.Many2one("res.partner") + + def _get_domain_adapter(self): + return [ + ("order_id.partner_id", "=", self.partner.id), + ("order_id.typology", "=", "sale"), + ] + + @property + def model_adapter(self) -> FilteredModelAdapter[SaleOrderLine]: + return FilteredModelAdapter[SaleOrderLine](self.env, self._get_domain_adapter()) + + def _get(self, record_id) -> SaleOrderLine: + return self.model_adapter.get(record_id) + + def _search(self, paging, params) -> tuple[int, SaleOrderLine]: + return self.model_adapter.search_with_count( + params.to_odoo_domain(self.env), + limit=paging.limit, + offset=paging.offset, + ) diff --git a/shopinvader_api_sale/schemas.py b/shopinvader_api_sale/schemas.py new file mode 100644 index 0000000000..52ce7b26ff --- /dev/null +++ b/shopinvader_api_sale/schemas.py @@ -0,0 +1,48 @@ +# Copyright 2024 Akretion (http://www.akretion.com). +# @author Florian Mounier +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). +from typing import Annotated + +from extendable_pydantic import StrictExtendableBaseModel +from pydantic import Field + +from odoo import api + +from odoo.addons.shopinvader_schema_sale.schemas.sale_line import SaleLine + + +class SaleLineWithSale(SaleLine): + order_id: int + + @classmethod + def from_sale_order_line(cls, odoo_rec): + res = super().from_sale_order_line(odoo_rec) + res.order_id = odoo_rec.order_id.id + return res + + +class SaleLineSearch(StrictExtendableBaseModel, extra="ignore"): + order_name: Annotated[ + str | None, + Field( + description="When used, the search look for any sale order lines " + "where the order name contains the given value case insensitively." + ), + ] = None + product_name: Annotated[ + str | None, + Field( + description="When used, the search look for any sale order lines " + "where the product name contains the given value case insensitively." + ), + ] = None + + def to_odoo_domain(self, env: api.Environment): + domain = [] + if self.order_name: + domain.append(("order_id.name", "ilike", self.order_name)) + + if self.product_name: + domain.append(("product_id.name", "ilike", self.product_name)) + + return domain diff --git a/shopinvader_api_sale/tests/test_shopinvader_sale_api.py b/shopinvader_api_sale/tests/test_shopinvader_sale_api.py index f3727ac559..68a7d16a3c 100644 --- a/shopinvader_api_sale/tests/test_shopinvader_sale_api.py +++ b/shopinvader_api_sale/tests/test_shopinvader_sale_api.py @@ -8,7 +8,7 @@ from odoo.addons.extendable_fastapi.tests.common import FastAPITransactionCase -from ..routers import sale_router +from ..routers import sale_line_router, sale_router @tagged("post_install", "-at_install") @@ -94,3 +94,33 @@ def test_download(self): response: Response = test_client.get(f"/sales/{sale.id}/download") self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.headers["Content-Type"], "application/pdf") + + def test_search_sale_lines(self): + so1 = self.env["sale.order"].create( + {"partner_id": self.default_fastapi_authenticated_partner.id} + ) + so1.write( + { + "order_line": [ + (0, 0, {"product_id": self.product_1.id, "product_uom_qty": 2}), + (0, 0, {"product_id": self.product_2.id, "product_uom_qty": 6}), + ] + } + ) + so2 = self.env["sale.order"].create( + {"partner_id": self.default_fastapi_authenticated_partner.id} + ) + so2.write( + { + "order_line": [ + (0, 0, {"product_id": self.product_1.id, "product_uom_qty": 1}), + (0, 0, {"product_id": self.product_2.id, "product_uom_qty": 3}), + (0, 0, {"product_id": self.product_1.id, "product_uom_qty": 4}), + ] + } + ) + + with self._create_test_client(router=sale_line_router) as test_client: + response: Response = test_client.get("/sale_lines") + self.assertEqual(response.status_code, status.HTTP_200_OK) + self.assertEqual(response.json()["count"], 5)