File: combo_configurator.py

package info (click to toggle)
odoo 18.0.0%2Bdfsg-2
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 878,716 kB
  • sloc: javascript: 927,937; python: 685,670; xml: 388,524; sh: 1,033; sql: 415; makefile: 26
file content (205 lines) | stat: -rw-r--r-- 8,907 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# Part of Odoo. See LICENSE file for full copyright and licensing details.

from datetime import datetime

from odoo.http import Controller, request, route
from odoo.tools import groupby


class SaleComboConfiguratorController(Controller):

    @route(route='/sale/combo_configurator/get_data', type='json', auth='user')
    def sale_combo_configurator_get_data(
        self,
        product_tmpl_id,
        quantity,
        date,
        currency_id=None,
        company_id=None,
        pricelist_id=None,
        selected_combo_items=None,
        **kwargs,
    ):
        """ Return data about the specified combo product.

        :param int product_tmpl_id: The product for which to get data, as a `product.template` id.
        :param int quantity: The quantity of the product.
        :param str date: The date to use to compute prices.
        :param int|None currency_id: The currency to use to compute prices, as a `res.currency` id.
        :param int|None company_id: The company to use, as a `res.company` id.
        :param int|None pricelist_id: The pricelist to use to compute prices, as a
            `product.pricelist` id.
        :param list(dict) selected_combo_items: The selected combo items, in the following format:
            {
                'id': int,
                'no_variant_ptav_ids': list(int),
                'custom_ptavs': list({
                    'id': int,
                    'value': str,
                }),
            }
        :param dict kwargs: Locally unused data passed to `_get_configurator_display_price` and
            `_get_additional_configurator_data`.
        :rtype: dict
        :return: A dict containing data about the combo product.
        """
        if company_id:
            request.update_context(allowed_company_ids=[company_id])
        product_template = request.env['product.template'].browse(product_tmpl_id)
        currency = request.env['res.currency'].browse(currency_id)
        pricelist = request.env['product.pricelist'].browse(pricelist_id)
        date = datetime.fromisoformat(date)
        selected_combo_item_dict = {item['id']: item for item in selected_combo_items or []}

        return {
            'product_tmpl_id': product_tmpl_id,
            'display_name': product_template.display_name,
            'quantity': quantity,
            'price': product_template._get_configurator_display_price(
                product_template, quantity, date, currency, pricelist, **kwargs
            ),
            'combos': [{
                'id': combo.id,
                'name': combo.name,
                'combo_items': [
                   self. _get_combo_item_data(
                       combo,
                       combo_item,
                       selected_combo_item_dict.get(combo_item.id, {}),
                       date,
                       currency,
                       pricelist,
                       **kwargs,
                   ) for combo_item in combo.combo_item_ids
                ],
            } for combo in product_template.combo_ids.sudo()],
            'currency_id': currency_id,
            **product_template._get_additional_configurator_data(
                product_template, date, currency, pricelist, **kwargs
            ),
        }

    @route(route='/sale/combo_configurator/get_price', type='json', auth='user')
    def sale_combo_configurator_get_price(
        self,
        product_tmpl_id,
        quantity,
        date,
        currency_id=None,
        company_id=None,
        pricelist_id=None,
        **kwargs,
    ):
        """ Return the price of the specified combo product.

        :param int product_tmpl_id: The product for which to get the price, as a `product.template`
            id.
        :param int quantity: The quantity of the product.
        :param str date: The date to use to compute the price.
        :param int|None currency_id: The currency to use to compute the price, as a `res.currency`
            id.
        :param int|None company_id: The company to use, as a `res.company` id.
        :param int|None pricelist_id: The pricelist to use to compute the price, as a
            `product.pricelist` id.
        :param dict kwargs: Locally unused data passed to `_get_configurator_display_price`.
        :rtype: float
        :return: The price of the combo product.
        """
        if company_id:
            request.update_context(allowed_company_ids=[company_id])
        product_template = request.env['product.template'].browse(product_tmpl_id)
        currency = request.env['res.currency'].browse(currency_id)
        pricelist = request.env['product.pricelist'].browse(pricelist_id)
        date = datetime.fromisoformat(date)

        return product_template._get_configurator_display_price(
            product_template, quantity, date, currency, pricelist, **kwargs
        )

    def _get_combo_item_data(
        self, combo, combo_item, selected_combo_item, date, currency, pricelist, **kwargs
    ):
        """ Return the price of the specified combo product.

        :param product.combo combo: The combo for which to get the data.
        :param product.combo.item combo_item: The combo for which to get the data.
        :param datetime date: The date to use to compute prices.
        :param product.pricelist pricelist: The pricelist to use to compute prices.
        :param dict kwargs: Locally unused data passed to `_get_additional_configurator_data`.
        :rtype: dict
        :return: A dict containing data about the combo item.
        """
        ptals_data = self._get_ptals_data(combo_item.product_id, selected_combo_item)
        # If the combo choice has only one combo item, and that combo item can't be configured (i.e.
        # it has no `no_variant` attributes), then it should be preselected, as the user has to
        # select it anyway.
        is_preselected = len(combo.combo_item_ids) == 1 and not any(
            ptal['create_variant'] == 'no_variant' for ptal in ptals_data
        )

        return {
            'id': combo_item.id,
            'extra_price': combo_item.extra_price,
            'is_selected': bool(selected_combo_item),
            'is_preselected': is_preselected,
            'product': {
                'id': combo_item.product_id.id,
                'product_tmpl_id': combo_item.product_id.product_tmpl_id.id,
                'display_name': combo_item.product_id.display_name,
                'ptals': ptals_data,
                **request.env['product.template']._get_additional_configurator_data(
                    combo_item.product_id, date, currency, pricelist, **kwargs
                ),
            },
        }

    def _get_ptals_data(self, product, selected_combo_item):
        """ Return data about the PTALs of the specified product.

        :param product.product product: The product for which to get the PTALs.
        :param dict selected_combo_item: The selected combo item, in the following format:
            {
                'id': int,
                'no_variant_ptav_ids': list(int),
                'custom_ptavs': list({
                    'id': int,
                    'value': str,
                }),
            }
        :rtype: list(dict)
        :return: A list of dicts containing data about the specified product's PTALs.
        """
        variant_ptavs = product.product_template_attribute_value_ids
        no_variant_ptavs = request.env['product.template.attribute.value'].browse(
            selected_combo_item.get('no_variant_ptav_ids')
        )
        ptavs_by_ptal_id = dict(groupby(
            variant_ptavs + no_variant_ptavs, lambda ptav: ptav.attribute_line_id.id
        ))

        custom_ptavs = selected_combo_item.get('custom_ptavs', [])
        custom_value_by_ptav_id = {ptav['id']: ptav['value'] for ptav in custom_ptavs}

        return [{
            'id': ptal.id,
            'name': ptal.attribute_id.name,
            'create_variant': ptal.attribute_id.create_variant,
            'selected_ptavs': self._get_selected_ptavs_data(
                ptavs_by_ptal_id.get(ptal.id, []), custom_value_by_ptav_id
            ),
        } for ptal in product.attribute_line_ids]

    def _get_selected_ptavs_data(self, selected_ptavs, custom_value_by_ptav_id):
        """ Return data about the selected PTAVs of the specified product.

        :param list(product.template.attribute.value) selected_ptavs: The selected PTAVs.
        :param dict custom_value_by_ptav_id: A mapping from PTAV ids to custom values.
        :rtype: list(dict)
        :return: A list of dicts containing data about the specified PTAL's selected PTAVs.
        """
        return [{
            'id': ptav.id,
            'name': ptav.name,
            'price_extra': ptav.price_extra,
            'custom_value': custom_value_by_ptav_id.get(ptav.id),
        } for ptav in selected_ptavs]