Skip to content
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

16 add option rebased #1550

Open
wants to merge 3 commits into
base: 16.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 36 additions & 20 deletions shopinvader_api_cart/routers/cart.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# Copyright 2024 Camptocamp (http://www.camptocamp.com).
# @author Simone Orsi <[email protected]>
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
import json
from collections import OrderedDict
from typing import Annotated
from uuid import UUID
Expand Down Expand Up @@ -109,21 +110,30 @@ def _check_transactions(self, transactions: list[CartTransaction]):
)

@api.model
def _group_transactions_by_product_id(self, transactions: list[CartTransaction]):
def _group_transactions_by_product_and_option(
self, transactions: list[CartTransaction]
):
"""
Gather together transactions that are linked to the same product.
"""
# take an ordered dict to ensure to create lines into the same
# order as the transactions list
transactions_by_product_id = OrderedDict()
transactions_by_product_and_option = OrderedDict()
for trans in transactions:
product_id = trans.product_id
transactions = transactions_by_product_id.get(product_id)
# TODO test to remove json
options = (
json.dumps(trans.options.model_dump(exclude_unset=True), sort_keys=True)
if trans.options
else "{}"
)
transactions = transactions_by_product_and_option.get((product_id, options))
if not transactions:
# TODO why transactions var?
transactions = []
transactions_by_product_id[product_id] = transactions
transactions_by_product_and_option[(product_id, options)] = transactions
transactions.append(trans)
return transactions_by_product_id
return transactions_by_product_and_option

@api.model
def _apply_transactions_on_existing_cart_line(
Expand Down Expand Up @@ -202,23 +212,29 @@ def _apply_transactions(self, cart, transactions: list[CartTransaction]):
return
cart.ensure_one()
self._check_transactions(transactions=transactions)
transactions_by_product_id = self._group_transactions_by_product_id(
transactions=transactions
transactions_by_product_and_option = (
self._group_transactions_by_product_and_option(transactions=transactions)
)
update_cmds = []
# prefetch all products
self.env["product.product"].browse(transactions_by_product_id.keys())
# here we avoid that each on change on a line trigger all the
# recompute methods on the SO. These methods will be triggered
# by the orm into the 'write' process
for product_id, trxs in transactions_by_product_id.items():
line = cart._get_cart_line(product_id)
if line:
cmd = self._apply_transactions_on_existing_cart_line(line, trxs)
else:
cmd = self._apply_transactions_creating_new_cart_line(cart, trxs)
if cmd:
update_cmds.append(cmd)
with self.env.norecompute():
# prefetch all products
self.env["product.product"].browse(
[p[0] for p in transactions_by_product_and_option]
)
# here we avoid that each on change on a line trigger all the
# recompute methods on the SO. These methods will be triggered
# by the orm into the 'write' process
for (
product_id,
options,
), trxs in transactions_by_product_and_option.items():
line = cart._get_cart_line(product_id, json.loads(options))
if line:
cmd = self._apply_transactions_on_existing_cart_line(line, trxs)
else:
cmd = self._apply_transactions_creating_new_cart_line(cart, trxs)
if cmd:
update_cmds.append(cmd)
all_transaction_uuids = transaction_uuids = [
str(t.uuid) for t in transactions if t.uuid
]
Expand Down
3 changes: 3 additions & 0 deletions shopinvader_api_cart/schemas/cart.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@

from extendable_pydantic import StrictExtendableBaseModel

from odoo.addons.shopinvader_schema_sale.schemas.sale_line_option import SaleLineOption


class CartTransaction(StrictExtendableBaseModel):
uuid: UUID | None = None
qty: float
product_id: int
options: SaleLineOption | None = None


class CartSyncInput(StrictExtendableBaseModel, extra="ignore"):
Expand Down
24 changes: 24 additions & 0 deletions shopinvader_api_cart/tests/fake_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2024 Akretion
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from odoo import api, fields, models


class SaleOrderLine(models.Model):
_inherit = "sale.order.line"

note = fields.Char()


class ShopinvaderApiCartRouterHelper(models.AbstractModel):
_inherit = "shopinvader_api_cart.cart_router.helper"

@api.model
def _prepare_line_from_transactions(self, cart, transactions):
vals = super()._prepare_line_from_transactions(cart, transactions)
# if the qty delta (add and remove of a product) is 0 then the vals is None
if vals:
options = transactions[0].options
if options and options.note:
vals["note"] = options.note
return vals
63 changes: 63 additions & 0 deletions shopinvader_api_cart/tests/test_shopinvader_api_cart_option.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# Copyright 2022 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

import json
import random

from odoo_test_helper import FakeModelLoader

from odoo.addons.shopinvader_schema_sale.schemas import sale_line_option

from ..routers.cart import cart_router
from .common import CommonSaleCart

NUMBER = range(1, 3)


class SaleLineOption(sale_line_option.SaleLineOption, extends=True):
note: str | None = None

@classmethod
def _prepare_from_sale_order_line(cls, line):
vals = super()._prepare_from_sale_order_line(line)
if hasattr(line, "note") and line.note:
vals["note"] = line.note
return vals


class TestSaleCartOption(CommonSaleCart):
@classmethod
def setUpClass(cls):
super(TestSaleCartOption, cls).setUpClass()
cls.loader = FakeModelLoader(cls.env, cls.__module__)
cls.loader.backup_registry()
from .fake_model import SaleOrderLine, ShopinvaderApiCartRouterHelper

cls.loader.update_registry((SaleOrderLine,))
# cls.loader.update_registry((SaleOrderLine, ShopinvaderApiCartRouterHelper))
cls.loader.update_registry((ShopinvaderApiCartRouterHelper,))

@classmethod
def tearDownClass(cls):
cls.loader.restore_registry()
super(TestSaleCartOption, cls).tearDownClass()

def test_transactions(self) -> None:
so = self.env["sale.order"]._create_empty_cart(
self.default_fastapi_authenticated_partner.id
)
data = {
"transactions": [
{
"uuid": self.trans_uuid_1,
"product_id": self.product_1.id,
"qty": 1,
"options": {"note": str(random.Random(NUMBER))},
}
]
}
with self._create_test_client(router=cart_router) as test_client:
test_client.post(f"/{so.uuid}/sync", content=json.dumps(data))
line = so.order_line
self.assertEqual(1, line.product_uom_qty)
self.assertIn(line.note, ("1", "2"), "Note field should be 1 or 2")
12 changes: 9 additions & 3 deletions shopinvader_sale_cart/models/sale_order.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@


class SaleOrder(models.Model):

_inherit = "sale.order"

uuid = fields.Char(string="EShop Unique identifier", readonly=True)
Expand Down Expand Up @@ -75,7 +74,12 @@ def _create_empty_cart(self, partner_id):
vals = self._prepare_cart(partner_id)
return self.create(vals)

def _get_cart_line(self, product_id):
def _matching_line(self, product_id, options):
return self.order_line.filtered(
lambda l, product_id=product_id: l.product_id.id == product_id
)

def _get_cart_line(self, product_id, options):
"""
Return the sale order line of the cart associated to the given product.
"""
Expand All @@ -88,7 +92,9 @@ def _update_cart_lines_from_cart(self, cart):
self.ensure_one()
update_cmds = []
for cart_line in cart.order_line:
line = self._get_cart_line(cart_line.product_id.id)
line = self._get_cart_line(
cart_line.product_id.id, {}
) # FIXME pass options
if line:
new_qty = line.product_uom_qty + cart_line.product_uom_qty
vals = {"product_uom_qty": new_qty}
Expand Down
1 change: 1 addition & 0 deletions shopinvader_schema_sale/schemas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .delivery import DeliveryInfo
from .invoicing import InvoicingInfo
from .sale_line import SaleLine
from .sale_line_option import SaleLineOption
from .amount import SaleAmount
from .sale import Sale, SaleSearch
3 changes: 3 additions & 0 deletions shopinvader_schema_sale/schemas/sale_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from odoo.tools.float_utils import json_float_round

from .amount import SaleLineAmount
from .sale_line_option import SaleLineOption


class SaleLine(StrictExtendableBaseModel):
Expand All @@ -14,6 +15,7 @@ class SaleLine(StrictExtendableBaseModel):
name: str
amount: SaleLineAmount | None = None
qty: float
options: SaleLineOption | None = None

@classmethod
def from_sale_order_line(cls, odoo_rec):
Expand All @@ -26,4 +28,5 @@ def from_sale_order_line(cls, odoo_rec):
odoo_rec.product_uom_qty,
precision_digits=len(str(odoo_rec.product_uom.rounding).split(".")[1]),
),
options=SaleLineOption.from_sale_order_line(odoo_rec),
)
15 changes: 15 additions & 0 deletions shopinvader_schema_sale/schemas/sale_line_option.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Copyright 2023 Akretion (https://www.akretion.com).
# @author Sébastien BEAU <[email protected]>
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).

from extendable_pydantic import StrictExtendableBaseModel


class SaleLineOption(StrictExtendableBaseModel):
@classmethod
def _prepare_from_sale_order_line(cls, line):
return {}

@classmethod
def from_sale_order_line(cls, line):
return cls.model_validate(cls._prepare_from_sale_order_line(line))
Loading