Skip to content

Commit e4c09e6

Browse files
committedMar 14, 2024··
Cleanup, refacoring, minor validations
1 parent 41252b1 commit e4c09e6

File tree

7 files changed

+44
-190
lines changed

7 files changed

+44
-190
lines changed
 

‎src/viur/shop/modules/discount.py

+10-143
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33

44
from viur.core import db, errors
55
from viur.core.prototypes import List
6-
from viur.core.skeleton import SkeletonInstance, skeletonByKind
6+
from viur.core.skeleton import SkeletonInstance
77
from viur.shop.types import *
88
from .abstract import ShopModuleAbstract
99
from ..globals import SHOP_LOGGER
10-
from ..types.dc_scope import ConditionValidator, DiscountValidator
10+
from ..types.dc_scope import DiscountValidator
1111

1212
logger = SHOP_LOGGER.getChild(__name__)
1313

@@ -91,34 +91,16 @@ def apply(
9191
break
9292
else:
9393
logger.error(f"{dv = }")
94-
# for cm in cms:
95-
# logger.debug(f"{cm = }")
96-
# if dv is None:
97-
# logger.warning(f"[{cm=} IS NONE")
98-
# continue
99-
# for scope in cm.applicable_scopes:
100-
# if not scope.is_fulfilled:
101-
# logger.error(f"{scope = }")
102-
# # logger.error(f"{scope = } // {scope.discount_skel=} // {scope.condition_skel=} // {scope.cart_skel=}")
103-
# else:
104-
# logger.debug(f"{scope = }")
105-
106-
10794
else:
10895
raise errors.NotFound("No valid code found")
109-
return False
96+
11097
logger.debug(f"Using {discount_skel=}")
11198
logger.debug(f"Using {dv=}")
112-
# cms = [cm for cm in cms if cm.is_fulfilled]
113-
# if len(domains := {cm.condition_skel["application_domain"] for cm in cms}) > 1:
114-
# raise NotImplementedError(f"Ambiguous application_domains: {domains=}")
115-
116-
# cond_skel = cms[0].condition_skel
117-
# for cm in cms:
118-
# if cm.is_fulfilled
11999

120-
# application_domain = cond_skel["application_domain"]
121-
application_domain = dv.application_domain
100+
try:
101+
application_domain = dv.application_domain
102+
except KeyError:
103+
raise InvalidStateError("application_domain not set")
122104

123105
if discount_skel["discount_type"] == DiscountType.FREE_ARTICLE:
124106
cart_node_skel = self.shop.cart.cart_add(
@@ -187,15 +169,13 @@ def apply(
187169
}
188170
raise errors.NotImplemented(f'{discount_skel["discount_type"]=} is not implemented yet :(')
189171

190-
return discount_skel
191-
192172
def can_apply(
193173
self,
194174
skel: SkeletonInstance,
195175
cart_key: db.Key | None = None,
196176
code: str | None = None,
197177
as_automatically: bool = False,
198-
) -> tuple[bool, DiscountValidator]:
178+
) -> tuple[bool, DiscountValidator | None]:
199179
logger.debug(f'{skel["name"] = } // {skel["description"] = }')
200180
# logger.debug(f"{skel = }")
201181

@@ -208,127 +188,14 @@ def can_apply(
208188

209189
if not as_automatically and skel["activate_automatically"]:
210190
logger.info(f"is activate_automatically")
211-
return False, []
212-
213-
# TODO:
214-
"""
215-
class ScopeCondition:
216-
def precondition(self, skel):#
217-
def is_satisfied(self, skel):#
218-
register from project custom
219-
"""
191+
return False, None
220192

221193
dv = DiscountValidator()(cart_skel=cart, discount_skel=skel, code=code)
222194
return dv.is_fulfilled, dv
223195

224-
# We need the full skel with all bones (otherwise the refSkel would be to large)
225-
condition_skel: SkeletonInstance = skeletonByKind(skel.condition.kind)() # noqa
226-
res = []
227-
for condition in skel["condition"]:
228-
if not condition_skel.fromDB(condition["dest"]["key"]):
229-
logger.warning(f'Broken relation {condition=} in {skel["key"]}?!')
230-
res.append(None)
231-
continue
232-
sm = ConditionValidator()(cart_skel=cart, discount_skel=skel, condition_skel=condition_skel)
233-
res.append(sm)
234-
if skel["condition_operator"] == ConditionOperator.ONE_OF:
235-
if any(sm.is_fulfilled for sm in res):
236-
logger.debug("Any condition fulfilled")
237-
return True, res
238-
logger.debug("NOT ANY condition fulfilled")
239-
elif skel["condition_operator"] == ConditionOperator.ALL:
240-
if all(sm.is_fulfilled for sm in res):
241-
logger.debug("All conditions fulfilled")
242-
return True, res
243-
logger.debug("NOT ALL condition fulfilled")
244-
else:
245-
raise InvalidStateError(f'Invalid condition operator: {skel["condition_operator"]}')
246-
return False, res
247-
248-
'''
249-
for condition in skel["condition"]:
250-
if not condition_skel.fromDB(condition["dest"]["key"]):
251-
logger.warning(f'Broken relation {condition=} in {skel["key"]}?!')
252-
continue
253-
254-
# Check if one scope is in conflict, then we skip the entire condition
255-
# Therefore we're testing the negation of the desired scope!
256-
# But we only check the values that are set.
257-
if (
258-
cart is not None # TODO
259-
and condition_skel["scope_minimum_order_value"] is not None
260-
and condition_skel["scope_minimum_order_value"] > cart["total"]
261-
):
262-
logger.info(f"scope_minimum_order_value not reached")
263-
continue
264-
265-
now = utils.utcNow()
266-
if (
267-
condition_skel["scope_date_start"] is not None
268-
and condition_skel["scope_date_start"] > now
269-
):
270-
logger.info(f"scope_date_start not reached")
271-
continue
272-
273-
if (
274-
condition_skel["scope_date_end"] is not None
275-
and condition_skel["scope_date_end"] < now
276-
):
277-
logger.info(f"scope_date_end not reached")
278-
continue
279-
280-
if (condition_skel["scope_language"] is not None
281-
and condition_skel["scope_language"] != current.language.get()
282-
):
283-
logger.info(f"scope_language not reached")
284-
continue
285-
286-
if (
287-
cart is not None
288-
and condition_skel["scope_minimum_quantity"] is not None
289-
and condition_skel["scope_minimum_quantity"] > cart["total_quantity"]
290-
):
291-
logger.info(f"scope_minimum_quantity not reached")
292-
continue
293-
294-
# TODO: if not scope_combinable_other_discount and article.shop_current_discount is not None
295-
296-
if (
297-
condition_skel["code_type"] == CodeType.UNIVERSAL
298-
and condition_skel["scope_code"] != code
299-
):
300-
logger.info(f'scope_code UNIVERSAL not reached ({condition_skel["scope_code"]=} != {code=})')
301-
continue
302-
elif (
303-
condition_skel["code_type"] == CodeType.INDIVIDUAL
304-
):
305-
sub = (
306-
self.shop.discount_condition.viewSkel().all()
307-
.filter("parent_code.dest.__key__ =", condition_skel["key"])
308-
.getSkel()
309-
)
310-
logger.debug(f"{sub = }")
311-
if sub["quantity_used"] > 0:
312-
logger.info(f'code_type INDIVIDUAL not reached (sub already used)')
313-
continue
314-
# TODO: implement all scopes
315-
# TODO: recheck code against this condition (any condition relation could've caused the query match!)
316-
317-
# All checks are passed, we have a suitable condition
318-
break
319-
else:
320-
return False, None
321-
# TODO: depending on condition_operator we have to use continue or return False
322-
# TODO: implement combineable check
323-
'''
324-
325-
logger.debug(f"{condition=}")
326-
327-
return True, condition_skel
328-
329196
@property
330197
@functools.cache
331-
def current_automatically_discounts(self):
198+
def current_automatically_discounts(self) -> list[SkeletonInstance]:
332199
query = self.viewSkel().all().filter("activate_automatically =", True)
333200
discounts = []
334201
for skel in query.fetch(100):

‎src/viur/shop/modules/discount_condition.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import typing as t
55

66
from viur import toolkit
7-
from viur.core import current, db, tasks, translate
7+
from viur.core import current, db, tasks
88
from viur.core.prototypes import List
99
from viur.core.skeleton import SkeletonInstance
1010
from .abstract import ShopModuleAbstract

‎src/viur/shop/skeletons/cart.py

-7
Original file line numberDiff line numberDiff line change
@@ -249,13 +249,6 @@ class CartItemSkel(TreeSkel): # STATE: Complete (as in model)
249249
descr="shop_is_low_price",
250250
)
251251

252-
# @property
253-
# def shop_vat_value(self):
254-
# """Calculate the vat value based on price and vat rate"""
255-
# if not (vat := self["shop_vat"]):
256-
# return 0
257-
# return (vat["dest"]["rate"] or 0) / 100 * self["price_sale"]
258-
259252
@property
260253
def article_skel(self):
261254
return self["article"]["dest"]

‎src/viur/shop/skeletons/discount.py

+13-1
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,26 @@ class DiscountSkel(Skeleton): # STATE: Complete (as in model)
3838
lambda skel: (
3939
[ReadFromClientError(
4040
ReadFromClientErrorSeverity.Invalid,
41-
"ApplicationDomains all all conditions must be basket or article, not mixed",
41+
"ApplicationDomains of all conditions must be basket or article, not mixed",
4242
["condition"],
4343
["condition"],
4444
)]
4545
if len({condition["dest"]["application_domain"] for condition in skel["condition"]
4646
if condition["dest"]["application_domain"] != ApplicationDomain.ALL}) > 1
4747
else []
4848
),
49+
# ApplicationDomain must be the same
50+
lambda skel: (
51+
[ReadFromClientError(
52+
ReadFromClientErrorSeverity.Invalid,
53+
"ApplicationDomains not set in any condition",
54+
["condition"],
55+
["condition"],
56+
)]
57+
if len({condition["dest"]["application_domain"] for condition in skel["condition"]
58+
if condition["dest"]["application_domain"] != ApplicationDomain.ALL}) == 0
59+
else []
60+
),
4961
]
5062

5163
name = StringBone(

‎src/viur/shop/skeletons/order.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,12 @@
44
from viur.core.bones import *
55
from viur.core.skeleton import Skeleton
66
from viur.shop.types import *
7-
from ..globals import SHOP_LOGGER
7+
from ..globals import SHOP_LOGGER, SHOP_INSTANCE
88

99
logger = SHOP_LOGGER.getChild(__name__)
1010

1111

1212
def get_payment_providers() -> dict[str, str | translate]:
13-
from viur.shop.shop import SHOP_INSTANCE
1413
return {
1514
pp.name: translate(f"shop.payment_provider.{pp.name}")
1615
for pp in SHOP_INSTANCE.get().payment_providers

‎src/viur/shop/skeletons/shipping.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,12 @@
33
from viur.core.bones import *
44
from viur.core.i18n import translate
55
from viur.core.skeleton import Skeleton
6-
from ..globals import SHOP_LOGGER
6+
from ..globals import SHOP_LOGGER, SHOP_INSTANCE
77

88
logger = SHOP_LOGGER.getChild(__name__)
99

1010

1111
def get_suppliers() -> dict[str, str]:
12-
from viur.shop.shop import SHOP_INSTANCE
1312
return {
1413
supplier.key: supplier.name
1514
for supplier in SHOP_INSTANCE.get().suppliers

‎src/viur/shop/types/dc_scope.py

+18-34
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,6 @@ def __call__(
8484
code=code,
8585
)
8686
self.scope_instances.append(scope)
87-
# scope.validate()
88-
8987
# logger.debug(f"{self.scope_instances = }")
9088
return self
9189

@@ -104,13 +102,18 @@ def __repr__(self):
104102
return f"<{self.__class__.__name__} with {self.is_fulfilled=} for {self.condition_skel=} using {self.applicable_scopes=}>"
105103
return f"<{self.__class__.__name__} with {self.is_fulfilled=} for {self.discount_skel=}, {self.condition_skel=}, {self.cart_skel=} using {self.applicable_scopes=}>"
106104

105+
@classmethod
106+
def register(cls, scope: t.Type[DiscountConditionScope]):
107+
cls.scopes.append(scope)
108+
return scope
109+
107110

108111
class DiscountValidator:
109112

110113
def __init__(self):
111114
super().__init__()
112115
self._is_fulfilled = None
113-
self.condition_validator_instances :list[ConditionValidator] = []
116+
self.condition_validator_instances: list[ConditionValidator] = []
114117
self.cart_skel = None
115118
self.discount_skel = None
116119
self.condition_skels = []
@@ -129,7 +132,7 @@ def __call__(
129132
# We need the full skel with all bones (otherwise the refSkel would be to large)
130133
condition_skel_cls: t.Type[Skeleton] = skeletonByKind(discount_skel.condition.kind)
131134
for condition in discount_skel["condition"]:
132-
condition_skel: SkeletonInstance = condition_skel_cls()# noqa
135+
condition_skel: SkeletonInstance = condition_skel_cls() # noqa
133136
if not condition_skel.fromDB(condition["dest"]["key"]):
134137
logger.warning(f'Broken relation {condition=} in {discount_skel["key"]}?!')
135138
self.condition_skels.append(None) # TODO
@@ -168,11 +171,11 @@ def __repr__(self):
168171
return f"<{self.__class__.__name__} with {self.is_fulfilled=} for {self.discount_skel=}, {self.cart_skel=} using {self.condition_validator_instances=}>"
169172

170173

174+
@ConditionValidator.register
171175
class ScopeCode(DiscountConditionScope):
172176
def precondition(self) -> bool:
173177
return (
174178
self.condition_skel["code_type"] in {CodeType.INDIVIDUAL, CodeType.INDIVIDUAL}
175-
# self.condition_skel["scope_minimum_order_value"] is not None
176179
# and self.cart_skel is not None
177180
)
178181

@@ -199,9 +202,7 @@ def __call__(self) -> bool:
199202
return True
200203

201204

202-
ConditionValidator.scopes.append(ScopeCode)
203-
204-
205+
@ConditionValidator.register
205206
class ScopeMinimumOrderValue(DiscountConditionScope):
206207
def precondition(self) -> bool:
207208
return (
@@ -215,9 +216,7 @@ def __call__(self) -> bool:
215216
)
216217

217218

218-
ConditionValidator.scopes.append(ScopeMinimumOrderValue)
219-
220-
219+
@ConditionValidator.register
221220
class ScopeDateStart(DiscountConditionScope):
222221
def precondition(self) -> bool:
223222
return self.condition_skel["scope_date_start"] is not None
@@ -226,9 +225,7 @@ def __call__(self) -> bool:
226225
return self.condition_skel["scope_date_start"] <= utils.utcNow()
227226

228227

229-
ConditionValidator.scopes.append(ScopeDateStart)
230-
231-
228+
@ConditionValidator.register
232229
class ScopeDateEnd(DiscountConditionScope):
233230
def precondition(self) -> bool:
234231
return self.condition_skel["scope_date_end"] is not None
@@ -237,9 +234,7 @@ def __call__(self) -> bool:
237234
return self.condition_skel["scope_date_end"] >= utils.utcNow()
238235

239236

240-
ConditionValidator.scopes.append(ScopeDateEnd)
241-
242-
237+
@ConditionValidator.register
243238
class ScopeLanguage(DiscountConditionScope):
244239
def precondition(self) -> bool:
245240
return self.condition_skel["scope_language"] is not None
@@ -248,9 +243,7 @@ def __call__(self) -> bool:
248243
return self.condition_skel["scope_language"] == current.language.get()
249244

250245

251-
ConditionValidator.scopes.append(ScopeLanguage)
252-
253-
246+
@ConditionValidator.register
254247
class ScopeCountry(DiscountConditionScope):
255248
def precondition(self) -> bool:
256249
return (
@@ -263,9 +256,7 @@ def __call__(self) -> bool:
263256
return self.condition_skel["scope_country"] == self.cart_skel["shipping_address"]["dest"]["country"]
264257

265258

266-
ConditionValidator.scopes.append(ScopeCountry)
267-
268-
259+
@ConditionValidator.register
269260
class ScopeMinimumQuantity(DiscountConditionScope):
270261
def precondition(self) -> bool:
271262
return (
@@ -279,9 +270,7 @@ def __call__(self) -> bool:
279270
)
280271

281272

282-
ConditionValidator.scopes.append(ScopeMinimumQuantity)
283-
284-
273+
@ConditionValidator.register
285274
class ScopeCustomerGroup(DiscountConditionScope):
286275
def precondition(self) -> bool:
287276
return (
@@ -307,8 +296,7 @@ def __call__(self) -> bool:
307296
raise NotImplementedError
308297

309298

310-
ConditionValidator.scopes.append(ScopeCustomerGroup)
311-
299+
# @ConditionValidator.register TODO
312300
class ScopeCombinableLowPrice(DiscountConditionScope):
313301
def precondition(self) -> bool:
314302
return (
@@ -317,12 +305,11 @@ def precondition(self) -> bool:
317305
)
318306

319307
def __call__(self) -> bool:
320-
article_skel = ... # FIXME: how we get this?
308+
article_skel = ... # FIXME: how we get this?
321309
return not article_skel["shop_is_low_price"] or self.condition_skel["scope_combinable_low_price"]
322310

323311

324-
# ConditionValidator.scopes.append(ScopeCombinableLowPrice)
325-
312+
@ConditionValidator.register
326313
class ScopeArticle(DiscountConditionScope):
327314
def precondition(self) -> bool:
328315
return (
@@ -339,6 +326,3 @@ def __call__(self) -> bool:
339326
)
340327
logger.debug(f"<{len(leaf_skels)}>{leaf_skels = }")
341328
return len(leaf_skels) > 0
342-
343-
344-
ConditionValidator.scopes.append(ScopeArticle)

0 commit comments

Comments
 (0)
Please sign in to comment.