Skip to content

Commit

Permalink
Add displayUnit and measureValue fields for Box GraphQL type and data…
Browse files Browse the repository at this point in the history
… model
  • Loading branch information
pylipp committed Sep 26, 2024
1 parent 22704b1 commit f4b867d
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 13 deletions.
9 changes: 9 additions & 0 deletions back/boxtribute_server/business_logic/warehouse/box/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,15 @@ async def resolve_box_location(box_obj, info):
return


@box.field("measureValue")
def resolve_box_measure_value(box_obj, _):
if box_obj.display_unit_id is None:
# Boxes with size-products (i.e. clothing) don't have any measure value assigned
return
# Convert value from base dimension to front-end unit
return box_obj.display_unit.conversion_factor * box_obj.measure_value


@box.field("state")
def resolve_box_state(box_obj, _):
# Instead of a BoxState instance return an integer for EnumType conversion
Expand Down
24 changes: 16 additions & 8 deletions back/boxtribute_server/business_logic/warehouse/box/queries.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
from ariadne import QueryType
from peewee import JOIN

from ....authz import authorize, authorize_for_reading_box
from ....graph_ql.filtering import derive_box_filter
from ....graph_ql.pagination import load_into_page
from ....models.definitions.box import Box
from ....models.definitions.location import Location
from ....models.definitions.unit import Unit

query = QueryType()


@query.field("box")
def resolve_box(*_, label_identifier):
box = (
Box.select(Box, Location)
Box.select(Box, Location, Unit)
.join(Location)
.join(Unit, JOIN.LEFT_OUTER, src=Box) # for measureValue resolver
.where(Box.label_identifier == label_identifier)
.get()
)
Expand All @@ -25,13 +28,18 @@ def resolve_box(*_, label_identifier):
def resolve_boxes(*_, base_id, pagination_input=None, filter_input=None):
authorize(permission="stock:read", base_id=base_id)

selection = Box.select().join(
Location,
on=(
(Box.location == Location.id)
& (Location.base == base_id)
& ((Box.deleted_on == 0) | (Box.deleted_on.is_null()))
),
selection = (
# Omit Unit.id because it confuses .count() in _compute_total_count()
Box.select(Box, Unit.conversion_factor, Unit.symbol, Unit.name)
.join(
Location,
on=(
(Box.location == Location.id)
& (Location.base == base_id)
& ((Box.deleted_on == 0) | (Box.deleted_on.is_null()))
),
)
.join(Unit, JOIN.LEFT_OUTER, src=Box) # for measureValue resolver
)
filter_condition, selection = derive_box_filter(filter_input, selection=selection)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from ariadne import ObjectType
from peewee import JOIN

from ....authz import authorize
from ....graph_ql.filtering import derive_box_filter
from ....graph_ql.pagination import load_into_page
from ....models.definitions.box import Box
from ....models.definitions.unit import Unit

classic_location = ObjectType("ClassicLocation")

Expand All @@ -17,7 +19,10 @@ def resolve_location_default_box_state(location_obj, _):
@classic_location.field("boxes")
def resolve_location_boxes(location_obj, _, pagination_input=None, filter_input=None):
authorize(permission="stock:read", base_id=location_obj.base_id)
filter_condition, selection = derive_box_filter(filter_input)
selection = Box.select(Box, Unit) # for measureValue resolver
filter_condition, selection = derive_box_filter(filter_input, selection=selection)
selection = selection.join(Unit, JOIN.LEFT_OUTER, src=Box)

return load_into_page(
Box,
Box.location == location_obj.id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ type Box implements ItemsCollection {
product: Product
" If the box holds a 'measure' product (i.e. classified by a package measure like 500gr), its size is null "
size: Size
" Information about the unit that the measure shall be displayed in. If the box holds a product with size (e.g. clothing), its unit is null "
displayUnit: Unit
" The value of the measure, expressed in ``unit``. If the box holds a product with size (e.g. clothing), its measure value is null "
measureValue: Float
state: BoxState!
qrCode: QrCode
createdBy: User
Expand Down
19 changes: 18 additions & 1 deletion back/boxtribute_server/models/definitions/box.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from peewee import SQL, CharField, DateTimeField, IntegerField, TextField
from peewee import SQL, CharField, DateTimeField, DecimalField, IntegerField, TextField

from ...db import db
from ...enums import BoxState as BoxStateEnum
Expand All @@ -9,6 +9,7 @@
from .product import Product
from .qr_code import QrCode
from .size import Size
from .unit import Unit
from .user import User


Expand Down Expand Up @@ -93,6 +94,22 @@ class Box(db.Model): # type: ignore
on_update="CASCADE",
on_delete="RESTRICT",
)
# The unit that the measure is displayed in the front-end in
display_unit = UIntForeignKeyField(
column_name="display_unit_id",
field="id",
model=Unit,
null=True,
on_update="CASCADE",
on_delete="RESTRICT",
)
# The value of the measure in the dimension's base unit (kilogram for mass, liter
# for volume). Multiply by unit.conversion_factor to obtain value in FE unit
measure_value = DecimalField(
max_digits=36,
decimal_places=18,
null=True,
)

class Meta:
table_name = "stock"
Expand Down
8 changes: 6 additions & 2 deletions back/minimal.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2945,6 +2945,8 @@ CREATE TABLE `stock` (
`box_id` varchar(11) NOT NULL DEFAULT '',
`product_id` int(11) unsigned NOT NULL,
`size_id` int(11) unsigned NOT NULL,
`display_unit_id` int(11) unsigned DEFAULT NULL,
`measure_value` decimal(36,18) unsigned DEFAULT NULL,
`items` int(11) DEFAULT NULL,
`location_id` int(11) unsigned NOT NULL,
`distro_event_id` int(11) unsigned DEFAULT NULL,
Expand All @@ -2967,12 +2969,14 @@ CREATE TABLE `stock` (
KEY `created_by` (`created_by`),
KEY `modified_by` (`modified_by`),
KEY `distro_event_id` (`distro_event_id`),
KEY `display_unit_id` (`display_unit_id`),
CONSTRAINT `stock_ibfk_10` FOREIGN KEY (`created_by`) REFERENCES `cms_users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `stock_ibfk_11` FOREIGN KEY (`modified_by`) REFERENCES `cms_users` (`id`) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT `stock_ibfk_14` FOREIGN KEY (`location_id`) REFERENCES `locations` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `stock_ibfk_15` FOREIGN KEY (`product_id`) REFERENCES `products` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `stock_ibfk_16` FOREIGN KEY (`size_id`) REFERENCES `sizes` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `stock_ibfk_17` FOREIGN KEY (`distro_event_id`) REFERENCES `distro_events` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `stock_ibfk_18` FOREIGN KEY (`display_unit_id`) REFERENCES `units` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `stock_ibfk_3` FOREIGN KEY (`qr_id`) REFERENCES `qr` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT `stock_ibfk_9` FOREIGN KEY (`box_state_id`) REFERENCES `box_state` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=100000247 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;
Expand All @@ -2985,8 +2989,8 @@ CREATE TABLE `stock` (
LOCK TABLES `stock` WRITE;
/*!40000 ALTER TABLE `stock` DISABLE KEYS */;
INSERT INTO `stock` VALUES
(100000000,'328765',1163,68,50,100000002,NULL,100000000,'Cypress seed test box','2015-01-01 11:15:32',1,NULL,NULL,'0000-00-00 00:00:00',5),
(100000001,'235563',1165,68,50,100000005,NULL,100000001,'50 dummy products','2019-09-29 18:15:32',1,NULL,NULL,'0000-00-00 00:00:00',5);
(100000000,'328765',1163,68,NULL,NULL,50,100000002,NULL,100000000,'Cypress seed test box','2015-01-01 11:15:32',1,NULL,NULL,'0000-00-00 00:00:00',5),
(100000001,'235563',1165,68,NULL,NULL,50,100000005,NULL,100000001,'50 dummy products','2019-09-29 18:15:32',1,NULL,NULL,'0000-00-00 00:00:00',5);
/*!40000 ALTER TABLE `stock` ENABLE KEYS */;
UNLOCK TABLES;

Expand Down
5 changes: 5 additions & 0 deletions back/test/data/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
qr_code_for_not_delivered_box_data,
)
from .size import another_size_data, default_size_data
from .unit import gram_unit_data
from .user import default_user_data


Expand All @@ -31,6 +32,8 @@ def default_box_data():
"size": default_size_data()["id"],
"location": default_location_data()["id"],
"qr_code": default_qr_code_data()["id"],
"display_unit": None,
"measure_value": None,
}


Expand Down Expand Up @@ -165,6 +168,8 @@ def measure_product_box_data():
data["label_identifier"] = "88111177"
data["product"] = product_data()[7]["id"]
data["size"] = None
data["display_unit"] = gram_unit_data()["id"]
data["measure_value"] = 0.5
data["state"] = BoxState.InStock
return data

Expand Down
27 changes: 26 additions & 1 deletion back/test/endpoint_tests/test_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@


def test_box_query_by_label_identifier(
read_only_client, default_box, tags, in_transit_box, default_shipment_detail
read_only_client,
default_box,
tags,
in_transit_box,
default_shipment_detail,
measure_product_box,
):
# Test case 8.1.1
label_identifier = default_box["label_identifier"]
Expand All @@ -31,6 +36,8 @@ def test_box_query_by_label_identifier(
numberOfItems
product {{ id }}
size {{ id }}
displayUnit {{ id }}
measureValue
state
qrCode {{ id }}
createdBy {{ id }}
Expand All @@ -53,6 +60,8 @@ def test_box_query_by_label_identifier(
"numberOfItems": default_box["number_of_items"],
"product": {"id": str(default_box["product"])},
"size": {"id": str(default_box["size"])},
"displayUnit": None,
"measureValue": None,
"state": BoxState.InStock.name,
"qrCode": {"id": str(default_box["qr_code"])},
"createdBy": {"id": str(default_box["created_by"])},
Expand Down Expand Up @@ -87,6 +96,22 @@ def test_box_query_by_label_identifier(
queried_box = assert_successful_request(read_only_client, query)
assert queried_box == {"shipmentDetail": {"id": str(default_shipment_detail["id"])}}

label_identifier = measure_product_box["label_identifier"]
query = f"""query {{
box(labelIdentifier: "{label_identifier}") {{
product {{ id }}
size {{ id }}
displayUnit {{ id }}
measureValue
}} }}"""
box = assert_successful_request(read_only_client, query)
assert box == {
"product": {"id": str(measure_product_box["product"])},
"size": None,
"displayUnit": {"id": str(measure_product_box["display_unit"])},
"measureValue": 1000 * measure_product_box["measure_value"],
}


def test_box_query_by_qr_code(read_only_client, default_box, default_qr_code):
# Test case 8.1.5
Expand Down
4 changes: 4 additions & 0 deletions front/src/types/generated/graphql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ export type Box = ItemsCollection & {
createdBy?: Maybe<User>;
createdOn?: Maybe<Scalars['Datetime']>;
deletedOn?: Maybe<Scalars['Datetime']>;
/** Information about the unit that the measure shall be displayed in. If the box holds a product with size (e.g. clothing), its unit is null */
displayUnit?: Maybe<Unit>;
distributionEvent?: Maybe<DistributionEvent>;
/** Sorted by date, newest first */
history?: Maybe<Array<HistoryEntry>>;
Expand All @@ -209,6 +211,8 @@ export type Box = ItemsCollection & {
lastModifiedBy?: Maybe<User>;
lastModifiedOn?: Maybe<Scalars['Datetime']>;
location?: Maybe<Location>;
/** The value of the measure, expressed in ``unit``. If the box holds a product with size (e.g. clothing), its measure value is null */
measureValue?: Maybe<Scalars['Float']>;
numberOfItems?: Maybe<Scalars['Int']>;
product?: Maybe<Product>;
qrCode?: Maybe<QrCode>;
Expand Down

0 comments on commit f4b867d

Please sign in to comment.