Skip to content

Commit 395af9d

Browse files
author
shopinvader-git-bot
committed
Merge PR #14 into 16.0
Signed-off-by sebastienbeau
2 parents 7dca5bc + 5747fbd commit 395af9d

7 files changed

Lines changed: 226 additions & 9 deletions

File tree

shopinvader_api_delivery_carrier/__manifest__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,7 @@
5656
"security/acl_product_product.xml",
5757
"security/acl_stock_picking.xml",
5858
"security/acl_stock_picking_type.xml",
59+
"security/acl_stock_move.xml",
60+
"security/acl_stock_move_line.xml",
5961
],
6062
}

shopinvader_api_delivery_carrier/routers/delivery.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,22 +17,23 @@
1717
from odoo.addons.shopinvader_filtered_model.utils import FilteredModelAdapter
1818
from odoo.addons.stock.models.stock_picking import Picking as StockPicking
1919

20-
from ..schemas import Picking
20+
from ..schemas import Picking, PickingSearch
2121

2222
delivery_router = APIRouter(tags=["deliveries"])
2323

2424

2525
@delivery_router.get("/deliveries")
2626
def search(
27+
params: Annotated[PickingSearch, Depends()],
2728
env: Annotated[api.Environment, Depends(authenticated_partner_env)],
2829
partner: Annotated["ResPartner", Depends(authenticated_partner)],
29-
paging_: Annotated[Paging, Depends(paging)],
30+
paging: Annotated[Paging, Depends(paging)],
3031
) -> PagedCollection[Picking]:
3132
"""Return all outgoing Deliveries for the authenticated partner."""
3233
count, pickings = (
3334
env["shopinvader_api_delivery_carrier.delivery_router.helper"]
3435
.new({"partner": partner})
35-
._search(paging_)
36+
._search(paging, params)
3637
)
3738
return PagedCollection[Picking](
3839
count=count, items=[Picking.from_picking(picking) for picking in pickings]
@@ -45,10 +46,11 @@ class ShopinvaderApiDeliveryRouterHelper(models.AbstractModel):
4546

4647
partner = fields.Many2one("res.partner")
4748

49+
def _get_picking_sale_domain(self):
50+
return [("typology", "=", "sale"), ("partner_id", "=", self.partner.id)]
51+
4852
def _get_domain_adapter(self):
49-
sales = self.env["sale.order"].search(
50-
[("typology", "=", "sale"), ("partner_id", "=", self.partner.id)]
51-
)
53+
sales = self.env["sale.order"].search(self._get_picking_sale_domain())
5254
return [
5355
("sale_id", "in", sales.ids),
5456
("picking_type_id.code", "=", "outgoing"),
@@ -58,9 +60,9 @@ def _get_domain_adapter(self):
5860
def model_adapter(self) -> FilteredModelAdapter[StockPicking]:
5961
return FilteredModelAdapter[StockPicking](self.env, self._get_domain_adapter())
6062

61-
def _search(self, paging) -> tuple[int, StockPicking]:
63+
def _search(self, paging, params) -> tuple[int, StockPicking]:
6264
return self.model_adapter.search_with_count(
63-
[],
65+
domain=params.to_odoo_domain(self.env),
6466
limit=paging.limit,
6567
offset=paging.offset,
6668
)

shopinvader_api_delivery_carrier/schemas/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
DeliveryCarrierInput,
55
DeliveryCarrierSearch,
66
)
7-
from .picking import Picking
7+
from .picking import Picking, PickingSearch
88
from .delivery import DeliveryInfo, DeliveryAmount
99
from .amount import SaleAmount
1010
from .sale_line import SaleLine

shopinvader_api_delivery_carrier/schemas/picking.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,116 @@
22
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
33

44
from datetime import datetime
5+
from enum import Enum
6+
from typing import Annotated
57

68
from extendable_pydantic import StrictExtendableBaseModel
79
from pydantic import Field
810

11+
from odoo import api
12+
from odoo.tools.float_utils import json_float_round
13+
914
from ..schemas import DeliveryCarrier
1015

1116

17+
class PickingState(str, Enum):
18+
"""Enum for picking states."""
19+
20+
draft = "draft"
21+
waiting = "waiting"
22+
confirmed = "confirmed"
23+
assigned = "assigned"
24+
done = "done"
25+
cancel = "cancel"
26+
27+
28+
class PickingSearch(StrictExtendableBaseModel):
29+
name: Annotated[
30+
str | None,
31+
Field(
32+
description="Name of the picking. If not provided, all names are returned.",
33+
),
34+
] = None
35+
state: Annotated[
36+
PickingState | None,
37+
Field(
38+
description="State of the picking. If not provided, all states are returned.",
39+
),
40+
] = None
41+
tracking_reference: Annotated[
42+
str | None,
43+
Field(
44+
description="Tracking reference of the picking. If not provided, "
45+
"all references are returned.",
46+
),
47+
] = None
48+
carrier_id: Annotated[
49+
int | None,
50+
Field(
51+
description="ID of the carrier. If not provided, all carriers are returned.",
52+
),
53+
] = None
54+
sale_id: Annotated[
55+
int | None,
56+
Field(
57+
description="ID of the sale order. If not provided, all sales are returned.",
58+
),
59+
] = None
60+
61+
def to_odoo_domain(self, env: api.Environment):
62+
domain = []
63+
64+
if self.name:
65+
domain.append(("name", "=", self.name))
66+
if self.state:
67+
domain.append(("state", "=", self.state.value))
68+
if self.tracking_reference:
69+
domain.append(("carrier_tracking_ref", "=", self.tracking_reference))
70+
if self.carrier_id:
71+
domain.append(("carrier_id", "=", self.carrier_id))
72+
if self.sale_id:
73+
domain.append(("sale_id", "=", self.sale_id))
74+
return domain
75+
76+
77+
class PickingLine(StrictExtendableBaseModel):
78+
product_id: int
79+
product_name: str
80+
state: str
81+
qty: float
82+
qty_done: float
83+
84+
@classmethod
85+
def from_picking_line(cls, odoo_rec):
86+
return cls.model_construct(
87+
product_id=odoo_rec.product_id.id,
88+
product_name=odoo_rec.product_id.name,
89+
state=odoo_rec.state,
90+
qty=json_float_round(
91+
odoo_rec.product_uom_qty,
92+
precision_digits=len(str(odoo_rec.product_uom.rounding).split(".")[1]),
93+
),
94+
qty_done=json_float_round(
95+
odoo_rec.quantity_done,
96+
precision_digits=len(str(odoo_rec.product_uom.rounding).split(".")[1]),
97+
),
98+
)
99+
100+
12101
class Picking(StrictExtendableBaseModel):
13102
delivery_id: int
14103
name: str
104+
state: PickingState
15105
tracking_reference: str | None = None
106+
tracking_url: str | None = None
16107
delivery_date: datetime | None = Field(
17108
None, description="Date done or Scheduled Date"
18109
)
19110
carrier: DeliveryCarrier | None = None
20111
sale_id: int | None = None
21112

113+
lines: list[PickingLine]
114+
22115
@classmethod
23116
def from_picking(cls, odoo_rec):
24117
delivery_date = None
@@ -29,10 +122,13 @@ def from_picking(cls, odoo_rec):
29122
return cls.model_construct(
30123
delivery_id=odoo_rec.id,
31124
name=odoo_rec.name,
125+
state=odoo_rec.state,
32126
tracking_reference=odoo_rec.carrier_tracking_ref or None,
127+
tracking_url=odoo_rec.carrier_tracking_url or None,
33128
delivery_date=delivery_date,
34129
carrier=DeliveryCarrier.from_delivery_carrier(odoo_rec.carrier_id)
35130
if odoo_rec.carrier_id
36131
else None,
37132
sale_id=odoo_rec.sale_id.id or None,
133+
lines=[PickingLine.from_picking_line(line) for line in odoo_rec.move_ids],
38134
)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<!--
3+
Copyright 2025 Akretion (http://www.akretion.com).
4+
@author Florian Mounier <florian.mounier@akretion.com>
5+
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
6+
-->
7+
<odoo>
8+
9+
<record model="ir.model.access" id="shopinvader_stock_move_access_view">
10+
<field name="name">stock.move shopinvader delivery user read access</field>
11+
<field name="model_id" ref="stock.model_stock_move" />
12+
<field
13+
name="group_id"
14+
ref="shopinvader_api_delivery_carrier.shopinvader_delivery_carrier_user_group"
15+
/>
16+
<field name="perm_read" eval="1" />
17+
<field name="perm_create" eval="0" />
18+
<field name="perm_unlink" eval="0" />
19+
<field name="perm_write" eval="0" />
20+
</record>
21+
22+
</odoo>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<!--
3+
Copyright 2025 Akretion (http://www.akretion.com).
4+
@author Florian Mounier <florian.mounier@akretion.com>
5+
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
6+
-->
7+
<odoo>
8+
9+
<record model="ir.model.access" id="shopinvader_stock_move_line_access_view">
10+
<field name="name">stock.move.line shopinvader delivery user read access</field>
11+
<field name="model_id" ref="stock.model_stock_move_line" />
12+
<field
13+
name="group_id"
14+
ref="shopinvader_api_delivery_carrier.shopinvader_delivery_carrier_user_group"
15+
/>
16+
<field name="perm_read" eval="1" />
17+
<field name="perm_create" eval="0" />
18+
<field name="perm_unlink" eval="0" />
19+
<field name="perm_write" eval="0" />
20+
</record>
21+
22+
</odoo>

shopinvader_api_delivery_carrier/tests/test_search_deliveries.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,14 @@ def _check_data_content(self, data, pickings):
117117
)
118118
else:
119119
self.assertFalse(current_data.get("delivery_date"))
120+
121+
self.assertEqual(len(current_data.get("lines")), len(picking.move_ids))
122+
for line_data, line in zip(current_data.get("lines"), picking.move_ids):
123+
self.assertEqual(line_data.get("product_id"), line.product_id.id)
124+
self.assertEqual(line_data.get("product_name"), line.product_id.name)
125+
self.assertEqual(line_data.get("state"), line.state)
126+
self.assertEqual(line_data.get("qty"), line.product_uom_qty)
127+
self.assertEqual(line_data.get("qty_done"), line.quantity_done)
120128
return True
121129

122130
def test_get_picking_logged_without_sale(self):
@@ -172,3 +180,68 @@ def test_get_multi_picking(self):
172180
self.assertEqual(response.status_code, 200)
173181
info = response.json()
174182
self._check_data_content(info["items"], pickings)
183+
184+
def test_get_picking_search_name(self):
185+
picking1 = self._create_picking(self.partner, sale=True)
186+
self._create_picking(self.partner, sale=True)
187+
188+
with self._create_test_client(router=delivery_router) as test_client:
189+
response: Response = test_client.get(
190+
"/deliveries", params={"name": picking1.name}
191+
)
192+
self.assertEqual(response.status_code, 200)
193+
info = response.json()
194+
self._check_data_content(info["items"], picking1)
195+
196+
def test_get_picking_search_tracking_ref(self):
197+
picking1 = self._create_picking(self.partner, sale=True)
198+
self._create_picking(self.partner, sale=True)
199+
self._fill_picking_optional_values(picking1)
200+
201+
with self._create_test_client(router=delivery_router) as test_client:
202+
response: Response = test_client.get(
203+
"/deliveries",
204+
params={"tracking_reference": picking1.carrier_tracking_ref},
205+
)
206+
self.assertEqual(response.status_code, 200)
207+
info = response.json()
208+
self._check_data_content(info["items"], picking1)
209+
210+
def test_get_picking_search_sale_id(self):
211+
picking1 = self._create_picking(self.partner, sale=True)
212+
self._create_picking(self.partner, sale=True)
213+
self._fill_picking_optional_values(picking1)
214+
215+
with self._create_test_client(router=delivery_router) as test_client:
216+
response: Response = test_client.get(
217+
"/deliveries", params={"sale_id": picking1.sale_id.id}
218+
)
219+
self.assertEqual(response.status_code, 200)
220+
info = response.json()
221+
self._check_data_content(info["items"], picking1)
222+
223+
def test_get_picking_search_carrier_id(self):
224+
picking1 = self._create_picking(self.partner, sale=True)
225+
self._create_picking(self.partner, sale=True)
226+
self._fill_picking_optional_values(picking1)
227+
228+
with self._create_test_client(router=delivery_router) as test_client:
229+
response: Response = test_client.get(
230+
"/deliveries", params={"carrier_id": picking1.carrier_id.id}
231+
)
232+
self.assertEqual(response.status_code, 200)
233+
info = response.json()
234+
self._check_data_content(info["items"], picking1)
235+
236+
def test_get_picking_search_state(self):
237+
picking1 = self._create_picking(self.partner, sale=True)
238+
self._create_picking(self.partner, sale=True)
239+
picking1.action_cancel()
240+
241+
with self._create_test_client(router=delivery_router) as test_client:
242+
response: Response = test_client.get(
243+
"/deliveries", params={"state": "cancel"}
244+
)
245+
self.assertEqual(response.status_code, 200)
246+
info = response.json()
247+
self._check_data_content(info["items"], picking1)

0 commit comments

Comments
 (0)