-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathinventory_service.py
More file actions
129 lines (109 loc) · 5.53 KB
/
inventory_service.py
File metadata and controls
129 lines (109 loc) · 5.53 KB
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
from datetime import datetime
from enum import Enum
from appkernel import MongoRepository, Model, Property, date_now_generator, create_uuid_generator, NotEmpty, \
AppKernelEngine, Role
from appkernel.configuration import config
from appkernel.model import action
from money import Money
from checkout.core.models import ProductSize, Product
class ReservationException(Exception):
def __init__(self, msg):
super().__init__(msg)
class Stock(Model, MongoRepository):
id = Property(str, generator=create_uuid_generator('S'))
product = Property(Product, required=True)
available = Property(int, required=True, default_value=0)
reserved = Property(int, required=True, default_value=0)
class ReservationState(Enum):
RESERVED = 1,
COMMITTED = 2,
EXECUTED = 3,
CANCELLED = 4
class Reservation(Model, MongoRepository):
id = Property(str, generator=create_uuid_generator('R'))
order_id = Property(str, required=True)
order_date = Property(datetime, required=True, generator=date_now_generator)
products = Property(list, sub_type=Product, required=True, validators=NotEmpty())
state = Property(ReservationState, required=True, default_value=ReservationState.RESERVED)
stocks = Property(dict, sub_type=dict)
tracking_id = Property(str)
def group_products_by_code(self):
products_by_code = dict()
for product in self.products:
size_and_quantity = products_by_code.get(product.code, {product.size.name: 0})
size_and_quantity[product.size.name] = size_and_quantity.get(product.size.name, 0) + 1
products_by_code[product.code] = size_and_quantity
return products_by_code
@classmethod
def before_post(cls, *args, **kwargs):
# method called before the reservation id is sent
for pcode, size_and_quantity in kwargs['model'].group_products_by_code().items():
for psize, quantity in size_and_quantity.items():
# todo: what if there are multiple stock items with the same product code
query = Stock.where(
(Stock.product.code == pcode) & (Stock.product.size == psize) & (Stock.available >= quantity))
reserved_stock = query.find_one_and_update(available=Stock.available - quantity,
reserved=Stock.reserved + quantity)
if not reserved_stock:
raise ReservationException(f"There's no stock available for code: {pcode} and size: {psize}.")
if hasattr(kwargs['model'], 'stocks') and kwargs['model'].stocks is not None:
size_and_qty = kwargs['model'].stocks.get(pcode, {psize: {'qty': quantity}})
if psize not in size_and_qty:
size_and_qty[psize] = {'qty': quantity}
else:
size_and_qty = {psize: {'qty': quantity}}
kwargs['model'].stocks = {}
size_and_qty[psize]['stock'] = reserved_stock.id
kwargs['model'].stocks[pcode] = size_and_qty
@classmethod
def after_post(cls, *args, **kwargs):
reservation: Reservation = kwargs.get('model')
print(f'reservation id: {reservation.id}')
@action(method='PATCH', require=[Role('user')])
def commit(self):
self.state = ReservationState.COMMITTED
self.save()
return self
@action(method='PATCH', require=[Role('user')])
def execute(self, tracking_id):
self.state = ReservationState.EXECUTED
self.tracking_id = tracking_id
self.save()
for pcode, sizes in self.stocks.items():
for size, quantity_and_stock in sizes.items():
stock_id = quantity_and_stock.get('stock')
query = Stock.where(Stock.id == stock_id)
modified_count = query.update_one(reserved=Stock.reserved - quantity_and_stock.get('qty'))
if modified_count != 1:
config.app_engine.logger.warn(f'quantities were not reduced for stock: {stock_id}')
return self
@action(method='PATCH', require=[Role('user')])
def cancel(self):
error = False
for pcode, sizes in self.stocks.items():
for size, quantity_and_stock in sizes.items():
stock_id = quantity_and_stock.get('stock')
query = Stock.where(Stock.id == stock_id)
qty = quantity_and_stock.get('qty')
modified_count = query.update_one(reserved=Stock.reserved - qty, available=Stock.available + qty)
if modified_count != 1:
error = True
config.app_engine.logger.warn(f'quantities were not reduced for stock: {stock_id}')
if not error:
self.state = ReservationState.CANCELLED
self.save()
return self
class InventoryService(object):
def __init__(self, appkernel: AppKernelEngine):
self.appkernel = appkernel
self.appkernel.register(Reservation, methods=['GET', 'POST', 'PUT'])
InventoryService.__initialise_inventory()
@staticmethod
def __initialise_inventory():
Stock.delete_all()
for code, tple in {'BTX': ('Black T-Shirt', 12.30), 'TRS': ('Trousers', 20.00), 'SHRT': ('Shirt', 72.30),
'NBS': ('Nice Black Shoe', 90.50)}.items():
for size in ProductSize:
stock = Stock(available=2,
product=Product(code=code, name=tple[0], size=size, price=Money(tple[1], 'EUR')))
stock.save()