From b06bfe636618f5738ebeef5280a3f09b645a576f Mon Sep 17 00:00:00 2001 From: Mustafa Abdulrahman Date: Sun, 28 May 2023 18:58:23 -0400 Subject: [PATCH 1/5] Added note alerting admins that modifying orders triggers emails --- .../TeamCheckedOutOrderTable/TeamCheckedOutOrderTable.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/hackathon_site/dashboard/frontend/src/components/teamDetail/TeamCheckedOutOrderTable/TeamCheckedOutOrderTable.tsx b/hackathon_site/dashboard/frontend/src/components/teamDetail/TeamCheckedOutOrderTable/TeamCheckedOutOrderTable.tsx index 8251050c5..648b60f60 100644 --- a/hackathon_site/dashboard/frontend/src/components/teamDetail/TeamCheckedOutOrderTable/TeamCheckedOutOrderTable.tsx +++ b/hackathon_site/dashboard/frontend/src/components/teamDetail/TeamCheckedOutOrderTable/TeamCheckedOutOrderTable.tsx @@ -11,6 +11,7 @@ import { TableContainer, TableHead, TableRow, + Typography, } from "@material-ui/core"; import React, { useState } from "react"; import Container from "@material-ui/core/Container"; @@ -386,6 +387,13 @@ export const TeamCheckedOutOrderTable = () => { spacing={1} style={{ marginTop: "10px" }} > + + + Note: participants will receive an + email every time you change the + status of their order. + + - - - + + + Note: participants will receive an email + every time you change the status of + their order. + + {pendingOrder.status === "Submitted" && ( + + + + )} + {pendingOrder.status === "Ready for Pickup" && ( + + + + )} + {pendingOrder.status === "Submitted" && ( + + + + )} + {pendingOrder.status === "Ready for Pickup" && ( + + + + + + + + )} diff --git a/hackathon_site/dashboard/frontend/src/pages/TeamDetail/TeamDetail.tsx b/hackathon_site/dashboard/frontend/src/pages/TeamDetail/TeamDetail.tsx index e3414405d..e9dc295ac 100644 --- a/hackathon_site/dashboard/frontend/src/pages/TeamDetail/TeamDetail.tsx +++ b/hackathon_site/dashboard/frontend/src/pages/TeamDetail/TeamDetail.tsx @@ -8,10 +8,7 @@ import { RouteComponentProps } from "react-router-dom"; import Header from "components/general/Header/Header"; import { Grid, Divider } from "@material-ui/core"; import Typography from "@material-ui/core/Typography"; -import { - AdminReturnedItemsTable, - SimplePendingOrderFulfillmentTable, -} from "components/teamDetail/SimpleOrderTables/SimpleOrderTables"; +import { AdminReturnedItemsTable } from "components/teamDetail/SimpleOrderTables/SimpleOrderTables"; import { errorSelector, getAdminTeamOrders, @@ -29,6 +26,7 @@ import TeamCheckedOutOrderTable from "components/teamDetail/TeamCheckedOutOrderT import { getHardwareWithFilters, setFilters } from "slices/hardware/hardwareSlice"; import { getCategories } from "slices/hardware/categorySlice"; import ProductOverview from "components/inventory/ProductOverview/ProductOverview"; +import TeamPendingOrderTable from "components/teamDetail/TeamPendingOrderTable/TeamPendingOrderTable"; export interface PageParams { code: string; @@ -91,7 +89,8 @@ const TeamDetail = ({ match }: RouteComponentProps) => { ) : ( <> - + {/**/} + diff --git a/hackathon_site/dashboard/frontend/src/slices/order/teamOrderSlice.ts b/hackathon_site/dashboard/frontend/src/slices/order/teamOrderSlice.ts index 2281071ea..8f9f8ee80 100644 --- a/hackathon_site/dashboard/frontend/src/slices/order/teamOrderSlice.ts +++ b/hackathon_site/dashboard/frontend/src/slices/order/teamOrderSlice.ts @@ -22,6 +22,10 @@ interface TeamOrderExtraState { export interface UpdateOrderAttributes { id: number; status: OrderStatus; + request?: { + id: number; + requested_quantity: number; + }[]; } const extraState: TeamOrderExtraState = { @@ -69,7 +73,8 @@ export const getAdminTeamOrders = createAsyncThunk< ); return rejectWithValue({ status: e.response.status, - message: e.response.message ?? e.response.data, + message: + e.response.message ?? e.response.data.status ?? e.response.data, }); } } @@ -228,12 +233,26 @@ const teamOrderSlice = createSlice({ builder.addCase(updateOrderStatus.fulfilled, (state, { payload }) => { state.isLoading = false; state.error = null; - const updateObject = { - id: payload.id, - changes: { - status: payload.status, - }, - }; + const { pendingOrders } = teamOrderListSerialization([payload]); + let updateObject; + if (pendingOrders.length > 0) { + const { hardwareInTableRow } = pendingOrders[0]; + + updateObject = { + id: payload.id, + changes: { + status: payload.status, + hardwareInTableRow, + }, + }; + } else { + updateObject = { + id: payload.id, + changes: { + status: payload.status, + }, + }; + } teamOrders.updateOne(state, updateObject); }); builder.addCase(updateOrderStatus.rejected, (state, { payload }) => { diff --git a/hackathon_site/hardware/migrations/0012_alter_orderitem_part_returned_health.py b/hackathon_site/hardware/migrations/0012_alter_orderitem_part_returned_health.py new file mode 100644 index 000000000..543423bbd --- /dev/null +++ b/hackathon_site/hardware/migrations/0012_alter_orderitem_part_returned_health.py @@ -0,0 +1,29 @@ +# Generated by Django 3.2.11 on 2023-06-10 18:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("hardware", "0011_alter_order_team"), + ] + + operations = [ + migrations.AlterField( + model_name="orderitem", + name="part_returned_health", + field=models.CharField( + blank=True, + choices=[ + ("Healthy", "Healthy"), + ("Heavily Used", "Heavily Used"), + ("Broken", "Broken"), + ("Lost", "Lost"), + ("Rejected", "Rejected"), + ], + max_length=64, + null=True, + ), + ), + ] diff --git a/hackathon_site/hardware/models.py b/hackathon_site/hardware/models.py index 241e5ed9e..5d3c9a3f1 100644 --- a/hackathon_site/hardware/models.py +++ b/hackathon_site/hardware/models.py @@ -28,6 +28,10 @@ def get_queryset(self): "order_items", filter=( ~Q(order_items__part_returned_health="Healthy") + & ~( + Q(order_items__part_returned_health="Rejected") + & Q(order_items__order__status="Picked Up") + ) & ~Q(order_items__order__status="Cancelled") ), distinct=True, @@ -85,6 +89,7 @@ class OrderItem(models.Model): ("Heavily Used", "Heavily Used"), ("Broken", "Broken"), ("Lost", "Lost"), + ("Rejected", "Rejected"), ] order = models.ForeignKey( "Order", null=False, on_delete=models.CASCADE, related_name="items" diff --git a/hackathon_site/hardware/serializers.py b/hackathon_site/hardware/serializers.py index 6392f300a..121467c8b 100644 --- a/hackathon_site/hardware/serializers.py +++ b/hackathon_site/hardware/serializers.py @@ -139,7 +139,7 @@ def get_team_code(obj: Order): class OrderChangeSerializer(OrderListSerializer): change_options = { "Submitted": ["Cancelled", "Ready for Pickup"], - "Ready for Pickup": ["Picked Up"], + "Ready for Pickup": ["Picked Up", "Submitted"], "Picked Up": ["Returned"], } @@ -169,6 +169,33 @@ def validate_status(self, data): ) return data + def update(self, instance: Order, validated_data): + status = validated_data.pop("status", None) + request = validated_data.pop("request", None) + + if status is not None: + instance.status = status + if request is not None: + for item in request: + items_in_order = list( + OrderItem.objects.filter(hardware=item["id"], order=instance.pk) + ) + num_items_in_order = len(items_in_order) + requested_number = item["requested_quantity"] + + if requested_number > num_items_in_order: + raise serializers.ValidationError( + f"Cannot increase the number of Hardware item number {items_in_order[0].hardware} to more than the originally ordered {requested_number} items." + ) + for idx in range(0, num_items_in_order): + if idx < num_items_in_order - requested_number: + items_in_order[idx].part_returned_health = "Rejected" + else: + items_in_order[idx].part_returned_health = None + items_in_order[idx].save() + + return serializers.ModelSerializer.update(self, instance, validated_data) + class TeamOrderChangeSerializer(OrderChangeSerializer): change_options = { diff --git a/hackathon_site/hardware/views.py b/hackathon_site/hardware/views.py index 8c283b21f..ecd9a0eb8 100644 --- a/hackathon_site/hardware/views.py +++ b/hackathon_site/hardware/views.py @@ -262,6 +262,8 @@ def patch(self, request, *args, **kwargs): connection.open() try: + if response.data["status"] == "Submitted": + return response render_to_string_context = { "recipient": "Hardware Inventory Admins", "order": response.data, From 45934f24101c2257da0c72b91e1edf6df686260f Mon Sep 17 00:00:00 2001 From: Mustafa Abdulrahman Date: Sat, 10 Jun 2023 18:15:07 -0400 Subject: [PATCH 4/5] Add limit for order searching and allow orders to exceed 1000 --- hackathon_site/dashboard/frontend/src/api/types.ts | 1 + .../src/components/orders/OrdersFilter/OrderFilter.tsx | 3 +++ .../dashboard/frontend/src/slices/order/adminOrderSlice.ts | 3 ++- hackathon_site/hackathon_site/settings/__init__.py | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/hackathon_site/dashboard/frontend/src/api/types.ts b/hackathon_site/dashboard/frontend/src/api/types.ts index ffcad8147..7a5c94a55 100644 --- a/hackathon_site/dashboard/frontend/src/api/types.ts +++ b/hackathon_site/dashboard/frontend/src/api/types.ts @@ -132,6 +132,7 @@ export interface OrderFilters { ordering?: OrderOrdering; status?: OrderStatus[]; search?: string; + limit?: number; } /** Sanitized Orders */ diff --git a/hackathon_site/dashboard/frontend/src/components/orders/OrdersFilter/OrderFilter.tsx b/hackathon_site/dashboard/frontend/src/components/orders/OrdersFilter/OrderFilter.tsx index 6d0225457..f67103b43 100644 --- a/hackathon_site/dashboard/frontend/src/components/orders/OrdersFilter/OrderFilter.tsx +++ b/hackathon_site/dashboard/frontend/src/components/orders/OrdersFilter/OrderFilter.tsx @@ -167,9 +167,11 @@ export const EnhancedOrderFilter = () => { const dispatch = useDispatch(); const handleSubmit = ({ ordering, status }: OrderFilters) => { + const limit = 1000; const filters: OrderFilters = { ordering, status, + limit, }; dispatch(setFilters(filters)); dispatch(getOrdersWithFilters()); @@ -185,6 +187,7 @@ export const EnhancedOrderFilter = () => { initialValues={{ ordering: "", status: [], + limit: 1000, }} onSubmit={handleSubmit} onReset={handleReset} diff --git a/hackathon_site/dashboard/frontend/src/slices/order/adminOrderSlice.ts b/hackathon_site/dashboard/frontend/src/slices/order/adminOrderSlice.ts index 82fab7a4c..a3342bbd1 100644 --- a/hackathon_site/dashboard/frontend/src/slices/order/adminOrderSlice.ts +++ b/hackathon_site/dashboard/frontend/src/slices/order/adminOrderSlice.ts @@ -88,7 +88,7 @@ const adminOrderSlice = createSlice({ state: AdminOrderState, { payload }: PayloadAction ) => { - const { status, ordering, search } = { + const { status, ordering, search, limit } = { ...state.filters, ...payload, }; @@ -98,6 +98,7 @@ const adminOrderSlice = createSlice({ ...(status && { status }), ...(ordering && { ordering }), ...(search && { search }), + ...(limit && { limit }), }; }, diff --git a/hackathon_site/hackathon_site/settings/__init__.py b/hackathon_site/hackathon_site/settings/__init__.py index ebfb97519..f7bcef63a 100644 --- a/hackathon_site/hackathon_site/settings/__init__.py +++ b/hackathon_site/hackathon_site/settings/__init__.py @@ -196,7 +196,7 @@ ], "DEFAULT_PAGINATION_CLASS": "rest_framework.pagination.LimitOffsetPagination", "DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"], - "PAGE_SIZE": 100, + "PAGE_SIZE": 1000, "DEFAULT_FILTER_BACKENDS": ["django_filters.rest_framework.DjangoFilterBackend",], } From b212e05403d6c77194d4537a63e41f89dbe9f5d1 Mon Sep 17 00:00:00 2001 From: Mustafa Abdulrahman Date: Sun, 18 Jun 2023 23:54:42 -0400 Subject: [PATCH 5/5] fix image missing issue --- .../teamDetail/TeamPendingOrderTable/TeamPendingOrderTable.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hackathon_site/dashboard/frontend/src/components/teamDetail/TeamPendingOrderTable/TeamPendingOrderTable.tsx b/hackathon_site/dashboard/frontend/src/components/teamDetail/TeamPendingOrderTable/TeamPendingOrderTable.tsx index 03e50a16a..e68020386 100644 --- a/hackathon_site/dashboard/frontend/src/components/teamDetail/TeamPendingOrderTable/TeamPendingOrderTable.tsx +++ b/hackathon_site/dashboard/frontend/src/components/teamDetail/TeamPendingOrderTable/TeamPendingOrderTable.tsx @@ -209,6 +209,8 @@ export const TeamPendingOrderTable = () => { src={ hardware[row.id] ?.picture ?? + hardware[row.id] + ?.image_url ?? hardwareImagePlaceholder } alt={