odoo13-addon-shopinvader-wishlist 13.0.3.4.2.dev1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- odoo/addons/shopinvader_wishlist/README.rst +69 -0
- odoo/addons/shopinvader_wishlist/__init__.py +4 -0
- odoo/addons/shopinvader_wishlist/__manifest__.py +15 -0
- odoo/addons/shopinvader_wishlist/components/__init__.py +1 -0
- odoo/addons/shopinvader_wishlist/components/access_info.py +20 -0
- odoo/addons/shopinvader_wishlist/demo/product_set.xml +17 -0
- odoo/addons/shopinvader_wishlist/i18n/shopinvader_wishlist.pot +66 -0
- odoo/addons/shopinvader_wishlist/models/__init__.py +1 -0
- odoo/addons/shopinvader_wishlist/models/product_set.py +80 -0
- odoo/addons/shopinvader_wishlist/readme/CONTRIBUTORS.rst +2 -0
- odoo/addons/shopinvader_wishlist/readme/CREDITS.rst +4 -0
- odoo/addons/shopinvader_wishlist/readme/DESCRIPTION.rst +1 -0
- odoo/addons/shopinvader_wishlist/services/__init__.py +1 -0
- odoo/addons/shopinvader_wishlist/services/wishlist.py +550 -0
- odoo/addons/shopinvader_wishlist/static/description/icon.png +0 -0
- odoo/addons/shopinvader_wishlist/static/description/index.html +426 -0
- odoo/addons/shopinvader_wishlist/tests/__init__.py +2 -0
- odoo/addons/shopinvader_wishlist/tests/test_product_set.py +119 -0
- odoo/addons/shopinvader_wishlist/tests/test_wishlist.py +368 -0
- odoo/addons/shopinvader_wishlist/views/product_set.xml +15 -0
- odoo/addons/shopinvader_wishlist/wizard/__init__.py +1 -0
- odoo/addons/shopinvader_wishlist/wizard/product_set_add.py +24 -0
- odoo/addons/shopinvader_wishlist/wizard/product_set_add.xml +13 -0
- odoo13_addon_shopinvader_wishlist-13.0.3.4.2.dev1.dist-info/METADATA +86 -0
- odoo13_addon_shopinvader_wishlist-13.0.3.4.2.dev1.dist-info/RECORD +27 -0
- odoo13_addon_shopinvader_wishlist-13.0.3.4.2.dev1.dist-info/WHEEL +5 -0
- odoo13_addon_shopinvader_wishlist-13.0.3.4.2.dev1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
# Copyright 2019 Camptocamp (http://www.camptocamp.com).
|
|
2
|
+
# @author Simone Orsi <simone.orsi@camptocamp.com>
|
|
3
|
+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
|
|
4
|
+
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from functools import wraps
|
|
7
|
+
|
|
8
|
+
from werkzeug.exceptions import NotFound
|
|
9
|
+
|
|
10
|
+
from odoo import _, exceptions
|
|
11
|
+
from odoo.osv import expression
|
|
12
|
+
|
|
13
|
+
from odoo.addons.base_rest.components.service import to_int
|
|
14
|
+
from odoo.addons.component.core import Component
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def data_mode(func):
|
|
18
|
+
"""Decorator extend Cerberus schema dict w/ data mode params.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
@wraps(func)
|
|
22
|
+
def wrapped(*args, **kwargs):
|
|
23
|
+
service = args[0]
|
|
24
|
+
res = func(*args, **kwargs)
|
|
25
|
+
res.update(service._validate_data_mode())
|
|
26
|
+
return res
|
|
27
|
+
|
|
28
|
+
return wrapped
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class WishlistService(Component):
|
|
32
|
+
"""Shopinvader service to manage current user's wishlists.
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
_name = "shopinvader.wishlist.service"
|
|
36
|
+
_inherit = "base.shopinvader.service"
|
|
37
|
+
_usage = "wishlist"
|
|
38
|
+
_expose_model = "product.set"
|
|
39
|
+
_description = __doc__
|
|
40
|
+
|
|
41
|
+
# The following method are 'public' and can be called from the controller.
|
|
42
|
+
# All params are untrusted so please check it !
|
|
43
|
+
|
|
44
|
+
def get(self, _id, **params):
|
|
45
|
+
record = self._get(_id)
|
|
46
|
+
return self._to_json_one(record, **params)
|
|
47
|
+
|
|
48
|
+
def search(self, **params):
|
|
49
|
+
return self._paginate_search(**params)
|
|
50
|
+
|
|
51
|
+
# pylint: disable=W8106
|
|
52
|
+
def create(self, **params):
|
|
53
|
+
if not self._is_logged_in():
|
|
54
|
+
# TODO: is there any way to control this in the REST API?
|
|
55
|
+
raise exceptions.UserError(
|
|
56
|
+
_("Must be authenticated to create a wishlist")
|
|
57
|
+
)
|
|
58
|
+
vals = self._prepare_params(params.copy())
|
|
59
|
+
record = self.env[self._expose_model].create(vals)
|
|
60
|
+
self._post_create(record)
|
|
61
|
+
return {"data": self._to_json_one(record, **params)}
|
|
62
|
+
|
|
63
|
+
def update(self, _id, **params):
|
|
64
|
+
record = self._get(_id)
|
|
65
|
+
record.write(self._prepare_params(params.copy(), mode="update"))
|
|
66
|
+
self._post_update(record)
|
|
67
|
+
return self.search(**params)
|
|
68
|
+
|
|
69
|
+
def delete(self, _id, **params):
|
|
70
|
+
self._get(_id).unlink()
|
|
71
|
+
return self.search(**params)
|
|
72
|
+
|
|
73
|
+
def add_to_cart(self, _id):
|
|
74
|
+
record = self._get(_id)
|
|
75
|
+
cart_service = self.component(usage="cart")
|
|
76
|
+
cart = cart_service._get()
|
|
77
|
+
self._add_to_cart(record, cart)
|
|
78
|
+
# return new cart
|
|
79
|
+
return cart_service._to_json(cart)
|
|
80
|
+
|
|
81
|
+
def add_items_to_cart(self, _id, **params):
|
|
82
|
+
record = self._get(_id)
|
|
83
|
+
cart_service = self.component(usage="cart")
|
|
84
|
+
cart = cart_service._get()
|
|
85
|
+
prod_ids = [x["product_id"] for x in params["lines"]]
|
|
86
|
+
lines = record.get_lines_by_products(product_ids=prod_ids)
|
|
87
|
+
self._add_items_to_cart(record, cart, lines)
|
|
88
|
+
# return new cart
|
|
89
|
+
return cart_service._to_json(cart)
|
|
90
|
+
|
|
91
|
+
def add_items(self, _id, **params):
|
|
92
|
+
record = self._get(_id)
|
|
93
|
+
self._add_items(record, params)
|
|
94
|
+
return self._to_json_one(record, **params)
|
|
95
|
+
|
|
96
|
+
def update_items(self, _id, **params):
|
|
97
|
+
record = self._get(_id)
|
|
98
|
+
self._update_items(record, params)
|
|
99
|
+
return self._to_json_one(record, **params)
|
|
100
|
+
|
|
101
|
+
def delete_items(self, _id, **params):
|
|
102
|
+
record = self._get(_id)
|
|
103
|
+
self._delete_items(record, params)
|
|
104
|
+
return self._to_json_one(record, **params)
|
|
105
|
+
|
|
106
|
+
def move_items(self, _id, **params):
|
|
107
|
+
record = self._get(_id)
|
|
108
|
+
self._move_items(record, params)
|
|
109
|
+
return self._to_json_one(record, **params)
|
|
110
|
+
|
|
111
|
+
def replace_items(self, _id, **params):
|
|
112
|
+
record = self._get(_id)
|
|
113
|
+
self._replace_items(record, params)
|
|
114
|
+
return self._to_json_one(record, **params)
|
|
115
|
+
|
|
116
|
+
@property
|
|
117
|
+
def access_info(self):
|
|
118
|
+
with self.shopinvader_backend.work_on(
|
|
119
|
+
"res.partner",
|
|
120
|
+
partner=self.partner,
|
|
121
|
+
partner_user=self.partner_user,
|
|
122
|
+
invader_partner=self.invader_partner,
|
|
123
|
+
invader_partner_user=self.invader_partner_user,
|
|
124
|
+
service_work=self.work,
|
|
125
|
+
) as work:
|
|
126
|
+
return work.component(usage="access.info")
|
|
127
|
+
|
|
128
|
+
def _post_create(self, record):
|
|
129
|
+
pass
|
|
130
|
+
|
|
131
|
+
def _post_update(self, record):
|
|
132
|
+
pass
|
|
133
|
+
|
|
134
|
+
def _validator_get(self):
|
|
135
|
+
return {}
|
|
136
|
+
|
|
137
|
+
def _validate_data_mode(self):
|
|
138
|
+
return {
|
|
139
|
+
"data_mode": {"type": "string", "nullable": True},
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
@data_mode
|
|
143
|
+
def _validator_search(self):
|
|
144
|
+
return {
|
|
145
|
+
"id": {"coerce": to_int, "type": "integer"},
|
|
146
|
+
"per_page": {
|
|
147
|
+
"coerce": to_int,
|
|
148
|
+
"nullable": True,
|
|
149
|
+
"type": "integer",
|
|
150
|
+
},
|
|
151
|
+
"page": {"coerce": to_int, "nullable": True, "type": "integer"},
|
|
152
|
+
"scope": {"type": "dict", "nullable": True},
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@data_mode
|
|
156
|
+
def _validator_create(self):
|
|
157
|
+
return {
|
|
158
|
+
"name": {"type": "string", "required": True},
|
|
159
|
+
"ref": {"type": "string", "required": False, "nullable": True},
|
|
160
|
+
"partner_id": {
|
|
161
|
+
"type": "integer",
|
|
162
|
+
"coerce": to_int,
|
|
163
|
+
"nullable": True,
|
|
164
|
+
},
|
|
165
|
+
"typology": {"type": "string", "nullable": True},
|
|
166
|
+
"lines": {
|
|
167
|
+
"type": "list",
|
|
168
|
+
"required": False,
|
|
169
|
+
"schema": {
|
|
170
|
+
"type": "dict",
|
|
171
|
+
"schema": self._validator_line_schema(),
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
def _validator_line_schema(self):
|
|
177
|
+
return {
|
|
178
|
+
"product_id": {
|
|
179
|
+
"coerce": to_int,
|
|
180
|
+
"required": True,
|
|
181
|
+
"type": "integer",
|
|
182
|
+
},
|
|
183
|
+
"quantity": {"coerce": float, "type": "float", "default": 1.0},
|
|
184
|
+
"sequence": {"coerce": int, "type": "integer", "required": False},
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
def _validator_update(self):
|
|
188
|
+
res = self._validator_create()
|
|
189
|
+
for key in res:
|
|
190
|
+
if "required" in res[key]:
|
|
191
|
+
del res[key]["required"]
|
|
192
|
+
return res
|
|
193
|
+
|
|
194
|
+
def _validator_add_to_cart(self):
|
|
195
|
+
return {"id": {"coerce": to_int, "type": "integer"}}
|
|
196
|
+
|
|
197
|
+
def _validator_add_items(self):
|
|
198
|
+
return {
|
|
199
|
+
"lines": {
|
|
200
|
+
"type": "list",
|
|
201
|
+
"required": True,
|
|
202
|
+
"schema": {
|
|
203
|
+
"type": "dict",
|
|
204
|
+
"schema": self._validator_add_item(),
|
|
205
|
+
},
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
def _validator_add_items_to_cart(self):
|
|
210
|
+
schema = self._validator_add_to_cart()
|
|
211
|
+
schema.update(
|
|
212
|
+
{
|
|
213
|
+
"lines": {
|
|
214
|
+
"type": "list",
|
|
215
|
+
"required": True,
|
|
216
|
+
"schema": {
|
|
217
|
+
"type": "dict",
|
|
218
|
+
"schema": {
|
|
219
|
+
"product_id": {
|
|
220
|
+
"coerce": to_int,
|
|
221
|
+
"required": True,
|
|
222
|
+
"type": "integer",
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
}
|
|
228
|
+
)
|
|
229
|
+
return schema
|
|
230
|
+
|
|
231
|
+
def _validator_add_item(self):
|
|
232
|
+
return self._validator_line_schema()
|
|
233
|
+
|
|
234
|
+
def _validator_update_items(self):
|
|
235
|
+
return self._validator_add_items()
|
|
236
|
+
|
|
237
|
+
@data_mode
|
|
238
|
+
def _validator_move_items(self):
|
|
239
|
+
return {
|
|
240
|
+
"lines": {
|
|
241
|
+
"type": "list",
|
|
242
|
+
"required": True,
|
|
243
|
+
"schema": {
|
|
244
|
+
"type": "dict",
|
|
245
|
+
"schema": self._validator_move_item(),
|
|
246
|
+
},
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
def _validator_move_item(self):
|
|
251
|
+
return {
|
|
252
|
+
"product_id": {
|
|
253
|
+
"coerce": to_int,
|
|
254
|
+
"required": True,
|
|
255
|
+
"type": "integer",
|
|
256
|
+
},
|
|
257
|
+
"move_to_wishlist_id": {
|
|
258
|
+
"coerce": to_int,
|
|
259
|
+
"required": True,
|
|
260
|
+
"type": "integer",
|
|
261
|
+
},
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@data_mode
|
|
265
|
+
def _validator_delete_items(self):
|
|
266
|
+
return {
|
|
267
|
+
"lines": {
|
|
268
|
+
"type": "list",
|
|
269
|
+
"required": True,
|
|
270
|
+
"schema": {
|
|
271
|
+
"type": "dict",
|
|
272
|
+
"schema": self._validator_delete_item(),
|
|
273
|
+
},
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
def _validator_delete_item(self):
|
|
278
|
+
return {
|
|
279
|
+
"product_id": {
|
|
280
|
+
"coerce": to_int,
|
|
281
|
+
"required": True,
|
|
282
|
+
"type": "integer",
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@data_mode
|
|
287
|
+
def _validator_replace_items(self):
|
|
288
|
+
return {
|
|
289
|
+
"lines": {
|
|
290
|
+
"type": "list",
|
|
291
|
+
"required": True,
|
|
292
|
+
"schema": {
|
|
293
|
+
"type": "dict",
|
|
294
|
+
"schema": self._validator_replace_item(),
|
|
295
|
+
},
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
def _validator_replace_item(self):
|
|
300
|
+
return {
|
|
301
|
+
# the item to replace
|
|
302
|
+
"product_id": {
|
|
303
|
+
"coerce": to_int,
|
|
304
|
+
"required": True,
|
|
305
|
+
"type": "integer",
|
|
306
|
+
},
|
|
307
|
+
# replace with this
|
|
308
|
+
"replacement_product_id": {
|
|
309
|
+
"coerce": to_int,
|
|
310
|
+
"required": True,
|
|
311
|
+
"type": "integer",
|
|
312
|
+
},
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
def _get_base_search_domain(self):
|
|
316
|
+
if not self._is_logged_in():
|
|
317
|
+
return expression.FALSE_DOMAIN
|
|
318
|
+
return self._default_domain_for_partner_records()
|
|
319
|
+
|
|
320
|
+
def _get_add_to_cart_wizard(self, record, cart):
|
|
321
|
+
return self.env["product.set.add"].create(
|
|
322
|
+
{
|
|
323
|
+
"order_id": cart.id,
|
|
324
|
+
"product_set_id": record.id,
|
|
325
|
+
"skip_existing_products": True,
|
|
326
|
+
}
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
def _add_to_cart(self, record, cart):
|
|
330
|
+
wizard = self._get_add_to_cart_wizard(record, cart)
|
|
331
|
+
return wizard.add_set()
|
|
332
|
+
|
|
333
|
+
def _add_items_to_cart(self, record, cart, lines):
|
|
334
|
+
wizard = self._get_add_to_cart_wizard(record, cart)
|
|
335
|
+
wizard.product_set_line_ids = lines
|
|
336
|
+
return wizard.add_set()
|
|
337
|
+
|
|
338
|
+
def _prepare_params(self, params, mode="create"):
|
|
339
|
+
if mode == "create":
|
|
340
|
+
params["shopinvader_backend_id"] = self.shopinvader_backend.id
|
|
341
|
+
if not params.get("partner_id"):
|
|
342
|
+
params["partner_id"] = self.partner_user.id
|
|
343
|
+
if not params.get("typology"):
|
|
344
|
+
params["typology"] = "wishlist"
|
|
345
|
+
record = self.env[self._expose_model].browse() # no record yet
|
|
346
|
+
params["set_line_ids"] = [
|
|
347
|
+
(0, 0, self._prepare_item(record, line))
|
|
348
|
+
for line in params.pop("lines", [])
|
|
349
|
+
]
|
|
350
|
+
params.pop("data_mode", None)
|
|
351
|
+
return params
|
|
352
|
+
|
|
353
|
+
def _to_json(self, records, data_mode=None, **kw):
|
|
354
|
+
if data_mode:
|
|
355
|
+
try:
|
|
356
|
+
parser = getattr(self, "_json_parser_" + data_mode)()
|
|
357
|
+
except AttributeError:
|
|
358
|
+
raise exceptions.UserError(
|
|
359
|
+
_("JSON data mode `%s` not found.") % data_mode
|
|
360
|
+
)
|
|
361
|
+
else:
|
|
362
|
+
parser = self._json_parser()
|
|
363
|
+
return records.jsonify(parser)
|
|
364
|
+
|
|
365
|
+
def _to_json_one(self, records, **kw):
|
|
366
|
+
# This works only here... see `_update_item` :/
|
|
367
|
+
records.set_line_ids.invalidate_cache()
|
|
368
|
+
values = self._to_json(records, **kw)
|
|
369
|
+
if len(records) == 1:
|
|
370
|
+
values = values[0]
|
|
371
|
+
return values
|
|
372
|
+
|
|
373
|
+
def _json_parser_light(self):
|
|
374
|
+
return [
|
|
375
|
+
"id",
|
|
376
|
+
"name",
|
|
377
|
+
("partner_id:partner", ["id", "name"]),
|
|
378
|
+
("partner_id:access", self._json_parser_wishlist_access),
|
|
379
|
+
]
|
|
380
|
+
|
|
381
|
+
def _json_parser(self):
|
|
382
|
+
return [
|
|
383
|
+
"id",
|
|
384
|
+
"name",
|
|
385
|
+
"typology",
|
|
386
|
+
"ref",
|
|
387
|
+
("partner_id:partner", ["id", "name"]),
|
|
388
|
+
("set_line_ids:lines", self._json_parser_line()),
|
|
389
|
+
("partner_id:access", self._json_parser_wishlist_access),
|
|
390
|
+
]
|
|
391
|
+
|
|
392
|
+
def _json_parser_line(self):
|
|
393
|
+
return [
|
|
394
|
+
"id",
|
|
395
|
+
"sequence",
|
|
396
|
+
"quantity",
|
|
397
|
+
("shopinvader_variant_id:product", self._json_parser_product_data),
|
|
398
|
+
]
|
|
399
|
+
|
|
400
|
+
def _json_parser_product_data(self, rec, fname):
|
|
401
|
+
if rec.shopinvader_variant_id:
|
|
402
|
+
data = rec.shopinvader_variant_id.get_shop_data()
|
|
403
|
+
data["available"] = rec.shopinvader_variant_id.active
|
|
404
|
+
return data
|
|
405
|
+
return rec.product_id.jsonify(
|
|
406
|
+
self._json_parser_binding_not_available_data(), one=True
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
def _json_parser_binding_not_available_data(self):
|
|
410
|
+
"""Special parser for when the binding is not available.
|
|
411
|
+
|
|
412
|
+
A user might delete bindings for a specific product
|
|
413
|
+
or add a product w/out bindings to a wishlist
|
|
414
|
+
and then archive the product which will lead to no binding as well.
|
|
415
|
+
Or, product and wishlists have been imported from CSVs
|
|
416
|
+
and the product was archived in the process or right after
|
|
417
|
+
before creating bindings.
|
|
418
|
+
|
|
419
|
+
In all the cases, you end up w/ broken reference.
|
|
420
|
+
|
|
421
|
+
When this happens, allow the frontend to show a nice message
|
|
422
|
+
to ask users to replace the product.
|
|
423
|
+
"""
|
|
424
|
+
return ["id", "name", ("active:available", lambda rec, fname: False)]
|
|
425
|
+
|
|
426
|
+
def _json_parser_wishlist_access(self, rec, fname):
|
|
427
|
+
return self.access_info.for_wishlist(rec)
|
|
428
|
+
|
|
429
|
+
def _get_existing_line(self, record, params, raise_if_not_found=False):
|
|
430
|
+
product_id = params["product_id"]
|
|
431
|
+
line = record.get_lines_by_products(product_ids=[product_id])
|
|
432
|
+
if not line and raise_if_not_found:
|
|
433
|
+
raise NotFound(
|
|
434
|
+
"No product found with id %s" % params["product_id"]
|
|
435
|
+
)
|
|
436
|
+
return line
|
|
437
|
+
|
|
438
|
+
def _update_lines(self, record, lines, raise_if_not_found=False):
|
|
439
|
+
new_items = []
|
|
440
|
+
for item_params in lines:
|
|
441
|
+
existing = self._get_existing_line(
|
|
442
|
+
record, item_params, raise_if_not_found=raise_if_not_found
|
|
443
|
+
)
|
|
444
|
+
if existing:
|
|
445
|
+
item_update_params = self._prepare_item(record, item_params)
|
|
446
|
+
# prevent move or prod change on update
|
|
447
|
+
item_update_params = {
|
|
448
|
+
k: v
|
|
449
|
+
for k, v in item_update_params.items()
|
|
450
|
+
if k not in ("product_set_id", "product_id")
|
|
451
|
+
}
|
|
452
|
+
existing.write(item_update_params)
|
|
453
|
+
else:
|
|
454
|
+
new_items.append(item_params)
|
|
455
|
+
values = [
|
|
456
|
+
(0, 0, self._prepare_item(record, item_params))
|
|
457
|
+
for item_params in new_items
|
|
458
|
+
]
|
|
459
|
+
if values:
|
|
460
|
+
record.write({"set_line_ids": values})
|
|
461
|
+
# TODO: WTF?? Cache on sequence is not invalidated.
|
|
462
|
+
# And calling this does not work here, must be called in `_to_json_one`.
|
|
463
|
+
# record.set_line_ids.invalidate_cache()
|
|
464
|
+
# flush does not work neither
|
|
465
|
+
# record.set_line_ids.flush()
|
|
466
|
+
|
|
467
|
+
def _add_items(self, record, params):
|
|
468
|
+
self._update_lines(record, params["lines"])
|
|
469
|
+
|
|
470
|
+
def _update_items(self, record, params):
|
|
471
|
+
self._update_lines(record, params["lines"], raise_if_not_found=True)
|
|
472
|
+
|
|
473
|
+
def _move_items(self, record, params):
|
|
474
|
+
# group lines by destination
|
|
475
|
+
by_destination = defaultdict(self.env["product.set.line"].browse)
|
|
476
|
+
to_delete = self.env["product.set.line"].browse()
|
|
477
|
+
for item_params in params["lines"]:
|
|
478
|
+
existing = self._get_existing_line(
|
|
479
|
+
record, item_params, raise_if_not_found=True
|
|
480
|
+
)
|
|
481
|
+
to_delete |= existing
|
|
482
|
+
by_destination[item_params["move_to_wishlist_id"]] += existing
|
|
483
|
+
|
|
484
|
+
# move all lines to each destination at once
|
|
485
|
+
for move_to_id, move_to_items in by_destination.items():
|
|
486
|
+
move_to_wl = self._get(move_to_id)
|
|
487
|
+
lines = move_to_items.read(
|
|
488
|
+
["product_id", "quantity", "sequence"], load="_classic_write"
|
|
489
|
+
)
|
|
490
|
+
values = [
|
|
491
|
+
(0, 0, self._prepare_item(move_to_wl, line)) for line in lines
|
|
492
|
+
]
|
|
493
|
+
move_to_wl.write({"set_line_ids": values})
|
|
494
|
+
# delete all old records
|
|
495
|
+
to_delete.unlink()
|
|
496
|
+
|
|
497
|
+
def _replace_items(self, record, params):
|
|
498
|
+
# get all lines
|
|
499
|
+
replace_lines = sorted(params["lines"], key=lambda x: x["product_id"])
|
|
500
|
+
product_ids = [x["product_id"] for x in replace_lines]
|
|
501
|
+
set_lines = record.set_line_ids.filtered(
|
|
502
|
+
lambda x: x.product_id.id in product_ids
|
|
503
|
+
)
|
|
504
|
+
lines_by_pid = {line.product_id.id: line.id for line in set_lines}
|
|
505
|
+
new_values = []
|
|
506
|
+
for line in replace_lines:
|
|
507
|
+
line_id = lines_by_pid.get(line["product_id"])
|
|
508
|
+
if not line_id:
|
|
509
|
+
continue
|
|
510
|
+
new_values.append((line_id, line["replacement_product_id"]))
|
|
511
|
+
|
|
512
|
+
# Update all lines at once to avoid tons of writes and tons of sync events
|
|
513
|
+
# TODO: probably this should be applied to all writes on lines.
|
|
514
|
+
# pylint: disable=sql-injection
|
|
515
|
+
query = """
|
|
516
|
+
UPDATE
|
|
517
|
+
product_set_line AS set_line
|
|
518
|
+
SET
|
|
519
|
+
product_id = c.product_id
|
|
520
|
+
FROM (VALUES {})
|
|
521
|
+
AS c(id, product_id)
|
|
522
|
+
WHERE c.id = set_line.id;
|
|
523
|
+
""".format(
|
|
524
|
+
",".join(["({}, {})".format(*x) for x in new_values])
|
|
525
|
+
)
|
|
526
|
+
self.env.cr.execute(query)
|
|
527
|
+
set_lines.invalidate_cache(["product_id", "shopinvader_variant_id"])
|
|
528
|
+
set_lines.recompute()
|
|
529
|
+
record.invalidate_cache(["set_line_ids"])
|
|
530
|
+
return set_lines
|
|
531
|
+
|
|
532
|
+
def _prepare_item(self, record, params):
|
|
533
|
+
vals = {
|
|
534
|
+
"product_set_id": record.id,
|
|
535
|
+
"product_id": params["product_id"],
|
|
536
|
+
"quantity": params.get("quantity") or 1,
|
|
537
|
+
}
|
|
538
|
+
if "sequence" in params:
|
|
539
|
+
# Set sequence only if explicitly given
|
|
540
|
+
vals["sequence"] = params.get("sequence")
|
|
541
|
+
return vals
|
|
542
|
+
|
|
543
|
+
def _delete_items(self, record, params):
|
|
544
|
+
to_delete = self.env["product.set.line"].browse()
|
|
545
|
+
for item_params in params["lines"]:
|
|
546
|
+
existing = self._get_existing_line(
|
|
547
|
+
record, item_params, raise_if_not_found=True
|
|
548
|
+
)
|
|
549
|
+
to_delete |= existing
|
|
550
|
+
to_delete.unlink()
|
|
Binary file
|