/
checkout_delivery_method_update.py
345 lines (317 loc) · 11.8 KB
/
checkout_delivery_method_update.py
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
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
from typing import Iterable, Optional
import graphene
from django.core.exceptions import ValidationError
from ....checkout.error_codes import CheckoutErrorCode
from ....checkout.fetch import (
CheckoutInfo,
CheckoutLineInfo,
fetch_checkout_info,
fetch_checkout_lines,
)
from ....checkout.utils import (
delete_external_shipping_id,
get_or_create_checkout_metadata,
invalidate_checkout_prices,
is_shipping_required,
set_external_shipping_id,
)
from ....plugins.webhook.utils import APP_ID_PREFIX
from ....shipping import interface as shipping_interface
from ....shipping import models as shipping_models
from ....shipping.utils import convert_to_shipping_method_data
from ....warehouse import WarehouseClickAndCollectOption
from ....warehouse import models as warehouse_models
from ....webhook.event_types import WebhookEventAsyncType, WebhookEventSyncType
from ...core import ResolveInfo
from ...core.descriptions import ADDED_IN_31, ADDED_IN_34, DEPRECATED_IN_3X_INPUT
from ...core.doc_category import DOC_CATEGORY_CHECKOUT
from ...core.mutations import BaseMutation
from ...core.scalars import UUID
from ...core.types import CheckoutError
from ...core.utils import WebhookEventInfo, from_global_id_or_error
from ...plugins.dataloaders import get_plugin_manager_promise
from ...shipping.types import ShippingMethod
from ...warehouse.types import Warehouse
from ..types import Checkout
from .utils import ERROR_DOES_NOT_SHIP, clean_delivery_method, get_checkout
class CheckoutDeliveryMethodUpdate(BaseMutation):
checkout = graphene.Field(Checkout, description="An updated checkout.")
class Arguments:
id = graphene.ID(
description="The checkout's ID." + ADDED_IN_34,
required=False,
)
token = UUID(
description=f"Checkout token.{DEPRECATED_IN_3X_INPUT} Use `id` instead.",
required=False,
)
delivery_method_id = graphene.ID(
description="Delivery Method ID (`Warehouse` ID or `ShippingMethod` ID).",
required=False,
)
class Meta:
description = (
"Updates the delivery method (shipping method or pick up point) "
"of the checkout. "
"Updates the checkout shipping_address for click and collect delivery "
"for a warehouse address. " + ADDED_IN_31
)
doc_category = DOC_CATEGORY_CHECKOUT
error_type_class = CheckoutError
webhook_events_info = [
WebhookEventInfo(
type=WebhookEventSyncType.SHIPPING_LIST_METHODS_FOR_CHECKOUT,
description=(
"Triggered when updating the checkout delivery method with "
"the external one."
),
),
WebhookEventInfo(
type=WebhookEventAsyncType.CHECKOUT_UPDATED,
description="A checkout was updated.",
),
]
@classmethod
def perform_on_shipping_method(
cls,
info: ResolveInfo,
shipping_method_id,
checkout_info,
lines,
checkout,
manager,
):
shipping_method = cls.get_node_or_error(
info,
shipping_method_id,
only_type=ShippingMethod,
field="delivery_method_id",
qs=shipping_models.ShippingMethod.objects.prefetch_related(
"postal_code_rules"
),
)
listing = shipping_models.ShippingMethodChannelListing.objects.filter(
shipping_method=shipping_method,
channel=checkout_info.channel,
).first()
if not listing:
raise ValidationError(
{
"delivery_method_id": ValidationError(
"This shipping method is not applicable in the given channel.",
code=CheckoutErrorCode.DELIVERY_METHOD_NOT_APPLICABLE.value,
)
}
)
delivery_method = convert_to_shipping_method_data(shipping_method, listing)
cls._check_delivery_method(
checkout_info, lines, shipping_method=delivery_method, collection_point=None
)
cls._update_delivery_method(
manager,
checkout_info,
lines,
shipping_method=shipping_method,
external_shipping_method=None,
collection_point=None,
)
return CheckoutDeliveryMethodUpdate(checkout=checkout)
@classmethod
def perform_on_external_shipping_method(
cls,
info: ResolveInfo,
shipping_method_id,
checkout_info,
lines,
checkout,
manager,
):
delivery_method = manager.get_shipping_method(
checkout=checkout,
channel_slug=checkout.channel.slug,
shipping_method_id=shipping_method_id,
)
if delivery_method is None and shipping_method_id:
raise ValidationError(
{
"delivery_method_id": ValidationError(
f"Couldn't resolve to a node: ${shipping_method_id}",
code=CheckoutErrorCode.NOT_FOUND.value,
)
}
)
cls._check_delivery_method(
checkout_info, lines, shipping_method=delivery_method, collection_point=None
)
cls._update_delivery_method(
manager,
checkout_info,
lines,
shipping_method=None,
external_shipping_method=delivery_method,
collection_point=None,
)
return CheckoutDeliveryMethodUpdate(checkout=checkout)
@classmethod
def perform_on_collection_point(
cls,
info: ResolveInfo,
collection_point_id,
checkout_info,
lines,
checkout,
manager,
):
collection_point = cls.get_node_or_error(
info,
collection_point_id,
only_type=Warehouse,
field="delivery_method_id",
qs=warehouse_models.Warehouse.objects.select_related("address"),
)
cls._check_delivery_method(
checkout_info,
lines,
shipping_method=None,
collection_point=collection_point,
)
cls._update_delivery_method(
manager,
checkout_info,
lines,
shipping_method=None,
external_shipping_method=None,
collection_point=collection_point,
)
return CheckoutDeliveryMethodUpdate(checkout=checkout)
@staticmethod
def _check_delivery_method(
checkout_info,
lines,
*,
shipping_method: Optional[shipping_interface.ShippingMethodData],
collection_point: Optional[Warehouse],
) -> None:
delivery_method = shipping_method
error_msg = "This shipping method is not applicable."
if collection_point is not None:
delivery_method = collection_point
error_msg = "This pick up point is not applicable."
delivery_method_is_valid = clean_delivery_method(
checkout_info=checkout_info, lines=lines, method=delivery_method
)
if not delivery_method_is_valid:
raise ValidationError(
{
"delivery_method_id": ValidationError(
error_msg,
code=CheckoutErrorCode.DELIVERY_METHOD_NOT_APPLICABLE.value,
)
}
)
@classmethod
def _update_delivery_method(
cls,
manager,
checkout_info: "CheckoutInfo",
lines: Iterable["CheckoutLineInfo"],
*,
shipping_method: Optional[ShippingMethod],
external_shipping_method: Optional[shipping_interface.ShippingMethodData],
collection_point: Optional[Warehouse],
) -> None:
checkout_fields_to_update = ["shipping_method", "collection_point"]
checkout = checkout_info.checkout
if external_shipping_method:
set_external_shipping_id(
checkout=checkout, app_shipping_id=external_shipping_method.id
)
else:
delete_external_shipping_id(checkout=checkout)
checkout.shipping_method = shipping_method
checkout.collection_point = collection_point
if (
collection_point is not None
and collection_point.click_and_collect_option
== WarehouseClickAndCollectOption.LOCAL_STOCK
):
checkout.shipping_address = collection_point.address
checkout_info.shipping_address = collection_point.address
checkout_fields_to_update += ["shipping_address"]
invalidate_prices_updated_fields = invalidate_checkout_prices(
checkout_info, lines, manager, save=False
)
checkout.save(
update_fields=checkout_fields_to_update + invalidate_prices_updated_fields
)
get_or_create_checkout_metadata(checkout).save()
cls.call_event(manager.checkout_updated, checkout)
@staticmethod
def _resolve_delivery_method_type(id_) -> Optional[str]:
if id_ is None:
return None
possible_types = ("Warehouse", "ShippingMethod", APP_ID_PREFIX)
type_, id_ = from_global_id_or_error(id_)
str_type = str(type_)
if str_type not in possible_types:
raise ValidationError(
{
"delivery_method_id": ValidationError(
"ID does not belong to Warehouse or ShippingMethod",
code=CheckoutErrorCode.INVALID.value,
)
}
)
return str_type
@classmethod
def perform_mutation(
cls,
_root,
info,
/,
token=None,
id=None,
delivery_method_id=None,
):
checkout = get_checkout(cls, info, checkout_id=None, token=token, id=id)
use_legacy_error_flow_for_checkout = (
checkout.channel.use_legacy_error_flow_for_checkout
)
manager = get_plugin_manager_promise(info.context).get()
lines, unavailable_variant_pks = fetch_checkout_lines(checkout)
if use_legacy_error_flow_for_checkout and unavailable_variant_pks:
not_available_variants_ids = {
graphene.Node.to_global_id("ProductVariant", pk)
for pk in unavailable_variant_pks
}
raise ValidationError(
{
"lines": ValidationError(
"Some of the checkout lines variants are unavailable.",
code=CheckoutErrorCode.UNAVAILABLE_VARIANT_IN_CHANNEL.value,
params={"variants": not_available_variants_ids},
)
}
)
if use_legacy_error_flow_for_checkout and not is_shipping_required(lines):
raise ValidationError(
{
"delivery_method": ValidationError(
ERROR_DOES_NOT_SHIP,
code=CheckoutErrorCode.SHIPPING_NOT_REQUIRED.value,
)
}
)
type_name = cls._resolve_delivery_method_type(delivery_method_id)
checkout_info = fetch_checkout_info(checkout, lines, manager)
if type_name == "Warehouse":
return cls.perform_on_collection_point(
info, delivery_method_id, checkout_info, lines, checkout, manager
)
if type_name == "ShippingMethod":
return cls.perform_on_shipping_method(
info, delivery_method_id, checkout_info, lines, checkout, manager
)
return cls.perform_on_external_shipping_method(
info, delivery_method_id, checkout_info, lines, checkout, manager
)