Skip to content

Commit

Permalink
Merge pull request #1375 from boxwise/mutations-for-manage-boxes-v2
Browse files Browse the repository at this point in the history
Add mutations for v2 ManageBoxes
  • Loading branch information
pylipp authored Jun 25, 2024
2 parents b44e3c1 + cca8ca5 commit ea5cb85
Show file tree
Hide file tree
Showing 26 changed files with 997 additions and 37 deletions.
14 changes: 10 additions & 4 deletions back/boxtribute_server/authz.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,19 +147,25 @@ def _authorize(


def authorized_bases_filter(
model: Type[Model] = Base, *, base_fk_field_name: str = "base"
model: Type[Model] = Base,
*,
base_fk_field_name: str = "base",
permission: Optional[str] = None,
) -> bool:
"""Derive base filter condition for given resource model depending the current
user's base-specific permissions. The resource model must have a FK field referring
to the Base model named 'base_fk_field_name'.
The lower-case model name must match the permission resource name.
The lower-case model name must match the permission resource name. Alternatively
it's possible to pass a specific permission of form 'resource:action'.
See also `auth.requires_auth()`.
"""
if g.user.is_god:
return True

resource = convert_pascal_to_snake_case(model.__name__)
permission = f"{resource}:read"
if not permission:
resource = convert_pascal_to_snake_case(model.__name__)
permission = f"{resource}:read"

_authorize(permission=permission, ignore_missing_base_info=True)
base_ids = g.user.authorized_base_ids(permission)
pattern = Base.id if model is Base else getattr(model, base_fk_field_name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
from ....models.definitions.shipment_detail import ShipmentDetail
from ....models.definitions.tags_relation import TagsRelation
from ....models.definitions.transfer_agreement import TransferAgreement
from ....models.utils import create_history_entries, utcnow
from ....models.utils import BATCH_SIZE, create_history_entries, utcnow
from ..agreement.crud import retrieve_transfer_agreement_bases


Expand Down Expand Up @@ -123,8 +123,12 @@ def _bulk_update_box_state(*, boxes, state, user_id, now):
box.state = state
box.last_modified_on = now
box.last_modified_by = user_id
Box.bulk_update(boxes, [Box.state, Box.last_modified_on, Box.last_modified_by])
DbChangeHistory.bulk_create(history_entries)
Box.bulk_update(
boxes,
[Box.state, Box.last_modified_on, Box.last_modified_by],
batch_size=BATCH_SIZE,
)
DbChangeHistory.bulk_create(history_entries, batch_size=BATCH_SIZE)


def cancel_shipment(*, shipment, user):
Expand Down Expand Up @@ -162,7 +166,9 @@ def cancel_shipment(*, shipment, user):
)
if details:
ShipmentDetail.bulk_update(
details, [ShipmentDetail.removed_on, ShipmentDetail.removed_by]
details,
[ShipmentDetail.removed_on, ShipmentDetail.removed_by],
batch_size=BATCH_SIZE,
)
shipment.save(only=[Shipment.state, Shipment.canceled_by, Shipment.canceled_on])
return shipment
Expand Down Expand Up @@ -308,7 +314,7 @@ def _remove_boxes_from_shipment(
_bulk_update_box_state(
boxes=[d.box for d in details], state=box_state, user_id=user_id, now=now
)
ShipmentDetail.bulk_update(details, fields=fields)
ShipmentDetail.bulk_update(details, fields=fields, batch_size=BATCH_SIZE)


def _update_shipment_with_received_boxes(
Expand Down Expand Up @@ -401,6 +407,7 @@ def _update_shipment_with_received_boxes(
Box.bulk_update(
checked_in_boxes,
[Box.state, Box.product, Box.location, Box.size, Box.number_of_items],
batch_size=BATCH_SIZE,
)
ShipmentDetail.bulk_update(
details,
Expand All @@ -412,12 +419,13 @@ def _update_shipment_with_received_boxes(
ShipmentDetail.received_on,
ShipmentDetail.received_by,
],
batch_size=BATCH_SIZE,
)
TagsRelation.delete().where(
(TagsRelation.object_type == TaggableObjectType.Box),
TagsRelation.object_id << [box.id for box in checked_in_boxes],
).execute()
DbChangeHistory.bulk_create(history_entries)
DbChangeHistory.bulk_create(history_entries, batch_size=BATCH_SIZE)


def _complete_shipment_if_applicable(*, shipment, user_id, now):
Expand Down Expand Up @@ -636,6 +644,7 @@ def move_not_delivered_boxes_in_stock(*, box_ids, user):
ShipmentDetail.removed_on,
ShipmentDetail.removed_by,
],
batch_size=BATCH_SIZE,
)

if shipment.state != ShipmentState.Completed:
Expand Down
2 changes: 1 addition & 1 deletion back/boxtribute_server/business_logic/statistics/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ def compute_stock_overview(base_id):
& (TagsRelation.object_type == TaggableObjectType.Box)
),
)
.where(Box.deleted.is_null())
.where((~Box.deleted_on) | (Box.deleted_on.is_null()))
.group_by(Box.id)
)
facts = (
Expand Down
155 changes: 154 additions & 1 deletion back/boxtribute_server/business_logic/warehouse/box/crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@
QrCodeAlreadyAssignedToBox,
)
from ....models.definitions.box import Box
from ....models.definitions.history import DbChangeHistory
from ....models.definitions.location import Location
from ....models.definitions.qr_code import QrCode
from ....models.definitions.tags_relation import TagsRelation
from ....models.utils import save_creation_to_history, save_update_to_history, utcnow
from ....models.utils import (
BATCH_SIZE,
save_creation_to_history,
save_update_to_history,
utcnow,
)
from ...tag.crud import assign_tag, unassign_tag

BOX_LABEL_IDENTIFIER_GENERATION_ATTEMPTS = 10
Expand Down Expand Up @@ -193,3 +199,150 @@ def update_box(
)

return box


def delete_boxes(*, user_id, boxes):
"""Soft-delete given boxes by setting the `deleted_on` field. Log every box deletion
in the history table.
Return the list of soft-deleted boxes.
"""
if not boxes:
# bulk_create() fails with an empty list, hence return immediately. Happens if
# all boxes requested for deletion are prohibited for the user, non-existing,
# and/or already deleted.
return []

now = utcnow()
history_entries = [
DbChangeHistory(
changes="Record deleted",
table_name=Box._meta.table_name,
record_id=box.id,
user=user_id,
change_date=now,
)
for box in boxes
]

box_ids = [box.id for box in boxes]
with db.database.atomic():
Box.update(deleted_on=now).where(Box.id << box_ids).execute()
DbChangeHistory.bulk_create(history_entries, batch_size=BATCH_SIZE)

# Re-fetch updated box data because returning "boxes" would contain outdated objects
# with the unset deleted_on field
return list(Box.select().where(Box.id << box_ids))


def move_boxes_to_location(*, user_id, boxes, location):
"""Update location and last_modified_* fields of the given boxes. If the new
location has a default box state assigned, change given boxes' state. Log every
location (and state) change in the history table.
Return the list of updated boxes.
"""
if not boxes:
return []

now = utcnow()
updated_fields = dict(
location=location.id, last_modified_on=now, last_modified_by=user_id
)
history_entries = [
DbChangeHistory(
changes=Box.location.column_name,
table_name=Box._meta.table_name,
record_id=box.id,
user=user_id,
change_date=now,
from_int=box.location_id,
to_int=location.id,
)
for box in boxes
if box.location_id != location.id
]

if location.box_state_id is not None:
updated_fields["state"] = location.box_state_id
history_entries.extend(
[
DbChangeHistory(
changes=Box.state.column_name,
table_name=Box._meta.table_name,
record_id=box.id,
user=user_id,
change_date=now,
from_int=box.state_id,
to_int=location.box_state_id,
)
for box in boxes
if box.state_id != location.box_state_id
]
)

box_ids = [box.id for box in boxes]
with db.database.atomic():
# Using Box.update(...).where(...) is better than Box.bulk_update() because the
# latter results in the less performant SQL
# UPDATE stock SET location_id = CASE stock.id
# WHEN 1 THEN 1
# WHEN 2 THEN 1
# ... END
# WHERE stock.id in (1, 2, ...);
# cf. https://docs.peewee-orm.com/en/latest/peewee/querying.html#alternatives
Box.update(**updated_fields).where(Box.id << box_ids).execute()
DbChangeHistory.bulk_create(history_entries, batch_size=BATCH_SIZE)

# Re-fetch updated box data because returning "boxes" would contain outdated objects
# with the old location
return list(Box.select().where(Box.id << box_ids))


def assign_tag_to_boxes(*, user_id, boxes, tag):
"""Add TagsRelation entries for given boxes and tag. Update last_modified_* fields
of the affected boxes.
Return the list of updated boxes.
"""
if not boxes:
return []

tags_relations = [
TagsRelation(
object_id=box.id,
object_type=TaggableObjectType.Box,
tag=tag.id,
)
for box in boxes
]

box_ids = [box.id for box in boxes]
with db.database.atomic():
Box.update(last_modified_on=utcnow(), last_modified_by=user_id).where(
Box.id << box_ids
).execute()
TagsRelation.bulk_create(tags_relations, batch_size=BATCH_SIZE)

# Skip re-fetching box data (last_modified_* fields will be outdated in response)
return boxes


def unassign_tag_from_boxes(*, user_id, boxes, tag):
"""Remove TagsRelation rows containing the given tag. Update last_modified_* fields
of the affected boxes.
Return the list of updated boxes.
"""
if not boxes:
return []

box_ids = [box.id for box in boxes]
with db.database.atomic():
Box.update(last_modified_on=utcnow(), last_modified_by=user_id).where(
Box.id << box_ids
).execute()
TagsRelation.delete().where(
TagsRelation.tag == tag.id,
TagsRelation.object_id << box_ids,
TagsRelation.object_type == TaggableObjectType.Box,
).execute()

# Skip re-fetching box data (last_modified_* fields will be outdated in response)
return boxes
Loading

0 comments on commit ea5cb85

Please sign in to comment.