Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions flask_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,20 @@ def allocate_endpoint():
return {"message": str(e)}, 400

return {"batchref": batchref}, 201



@app.route("/deallocate", methods=["POST"])
def deallocate_endpoint():
session = get_session()
repo = repository.SqlAlchemyRepository(session)

order_id = request.json["orderid"]
sku = request.json["sku"]

try:
batchref = services.deallocate(order_id, sku,repo, session)
except Exception as e:
return {"message": str(e)}, 400

return {"batchref": batchref}, 201
11 changes: 11 additions & 0 deletions repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ def add(self, batch: model.Batch):
@abc.abstractmethod
def get(self, reference) -> model.Batch:
raise NotImplementedError

@abc.abstractmethod
def get_line_item(self, order_id: str, sku: str) -> model.OrderLine:
raise NotImplementedError


class SqlAlchemyRepository(AbstractRepository):
Expand All @@ -24,3 +28,10 @@ def get(self, reference):

def list(self):
return self.session.query(model.Batch).all()

def get_line_item(self, order_id: str, sku: str) -> model.OrderLine:
return (
self.session.query(model.OrderLine)
.filter_by(orderid=order_id, sku=sku)
.one()
)
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
pytest
sqlalchemy
sqlalchemy==1.4.49
flask
psycopg2-binary
requests
25 changes: 25 additions & 0 deletions services.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from __future__ import annotations
import datetime
from typing import Optional

import model
from model import OrderLine
Expand All @@ -20,3 +22,26 @@ def allocate(line: OrderLine, repo: AbstractRepository, session) -> str:
batchref = model.allocate(line, batches)
session.commit()
return batchref

def deallocate(order_id:str, sku:str, repo:AbstractRepository,session):

order_line = repo.get_line_item(order_id, sku)

if not order_line :
raise Exception("Couldnt find the order line")

batches = repo.list()

for batch in batches:
if order_line in batch._allocations:
batch.deallocate(order_line)
break
else:
raise Exception("Couldnt find the order line in any batch")

session.commit()


def add_batch(ref:str, sku:str, qty:int, eta:Optional[datetime.date], repo:AbstractRepository, session):
repo.add(model.Batch(ref, sku, qty, eta))
session.commit()
8 changes: 4 additions & 4 deletions test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,16 +55,16 @@ def test_unhappy_path_returns_400_and_error_message():

@pytest.mark.usefixtures("postgres_db")
@pytest.mark.usefixtures("restart_api")
def test_deallocate():
def test_deallocate(add_stock):
sku, order1, order2 = random_sku(), random_orderid(), random_orderid()
batch = random_batchref()
post_to_add_batch(batch, sku, 100, "2011-01-02")
add_stock([(batch, sku, 100, "2011-01-02")])
url = config.get_api_url()
# fully allocate
r = requests.post(
f"{url}/allocate", json={"orderid": order1, "sku": sku, "qty": 100}
)
assert r.json()["batchid"] == batch
assert r.json()["batchref"] == batch

# cannot allocate second order
r = requests.post(
Expand All @@ -87,4 +87,4 @@ def test_deallocate():
f"{url}/allocate", json={"orderid": order2, "sku": sku, "qty": 100}
)
assert r.ok
assert r.json()["batchid"] == batch
assert r.json()["batchref"] == batch
57 changes: 53 additions & 4 deletions test_services.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from datetime import date

import pytest
import model
import repository
Expand All @@ -16,6 +18,13 @@ def get(self, reference):

def list(self):
return list(self._batches)

def get_line_item(self, order_id, sku):
for batch in self._batches:
for line in batch._allocations:
if line.orderid == order_id and line.sku == sku:
return line
Comment on lines +23 to +26
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't resist suggesting this formulation with next() and a nested list comp. lol

return next(
    (l for b in self._batches for l in b._allocations if l.orderid == order_id and l.sku == sku), 
    None,
)

return None


class FakeSession:
Expand Down Expand Up @@ -61,14 +70,54 @@ def test_deallocate_decrements_available_quantity():
services.allocate(line, repo, session)
batch = repo.get(reference="b1")
assert batch.available_quantity == 90
# services.deallocate(...
...
services.deallocate(line.orderid, line.sku, repo, session)

assert batch.available_quantity == 100


def test_deallocate_decrements_correct_quantity():
... # TODO - check that we decrement the right sku
"""Among batches with the same SKU, earliest ETA wins; deallocate must target that batch."""
repo, session = FakeRepository([]), FakeSession()
services.add_batch("b1", "BLUE-PLINTH", 100, date(2011, 1, 1), repo, session)
services.add_batch("b2", "BLUE-PLINTH", 100, date(2011, 1, 2), repo, session)
services.add_batch("b3", "RED-PLINTH", 100, None, repo, session)
line = model.OrderLine("o1", "BLUE-PLINTH", 10)
services.allocate(line, repo, session)
batch_b1 = repo.get(reference="b1")
assert batch_b1.available_quantity == 90
services.deallocate(line.orderid, line.sku, repo, session)

assert batch_b1.available_quantity == 100


def test_deallocate_needs_sku_when_one_order_has_several_line_items():

repo, session = FakeRepository([]), FakeSession()
services.add_batch("b-blue", "BLUE-PLINTH", 100, None, repo, session)
services.add_batch("b-red", "RED-PLINTH", 100, None, repo, session)

orderid = "ORD-MULTI"
line_blue = model.OrderLine(orderid, "BLUE-PLINTH", 10)
line_red = model.OrderLine(orderid, "RED-PLINTH", 7)

services.allocate(line_blue, repo, session)
services.allocate(line_red, repo, session)

batch_blue = repo.get(reference="b-blue")
batch_red = repo.get(reference="b-red")
assert batch_blue.available_quantity == 90
assert batch_red.available_quantity == 93

services.deallocate(orderid, "BLUE-PLINTH", repo, session)

assert batch_blue.available_quantity == 100
assert batch_red.available_quantity == 93


def test_trying_to_deallocate_unallocated_batch():
... # TODO: should this error or pass silently? up to you.
"""Deallocate fails when order line is not allocated to any batch."""
repo, session = FakeRepository([]), FakeSession()
services.add_batch("b1", "BLUE-PLINTH", 100, None, repo, session)
line = model.OrderLine("o1", "BLUE-PLINTH", 10)
with pytest.raises(Exception, match="Couldnt find the order line"):
services.deallocate(line.orderid, line.sku, repo, session)