Skip to content
Merged
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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ USER_ALT_USERNAME=alt
USER_ALT_PASSWORD=password
USER_MANAGER_USERNAME=manager
USER_MANAGER_PASSWORD=password
USER_IMPERSONATOR_USERNAME=impersonator
USER_IMPERSONATOR_PASSWORD=password
LOCATION_MAIN=locationId
LOCATION_NO_MANAGE_INVENOTRY_DEPOT=locationId
LOCATION_SUPPLIER=locationId
LOCATION_SUPPLIER_ALT=locationId
LOCATION_DEPOT=locationId
LOCATION_NO_PICK_AND_PUTAWAY_STOCK_DEPOT=locationId
LOCATION_INTERNAL=locationId
LOCATION_WARD=locationId
PRODUCT_ONE=productId
PRODUCT_TWO=productId
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ jobs:
LOCATION_DEPOT: ${{ secrets.LOCATION_DEPOT }}
LOCATION_WARD: ${{ secrets.LOCATION_WARD }}
LOCATION_NO_PICK_AND_PUTAWAY_STOCK_DEPOT: ${{ secrets.LOCATION_NO_PICK_AND_PUTAWAY_STOCK_DEPOT }}
LOCATION_INTERNAL: ${{ secrets.LOCATION_INTERNAL }}
PRODUCT_ONE: ${{ secrets.PRODUCT_ONE }}
PRODUCT_TWO: ${{ secrets.PRODUCT_TWO }}
PRODUCT_THREE: ${{ secrets.PRODUCT_THREE }}
Expand Down
140 changes: 102 additions & 38 deletions src/api/ReceivingService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import BaseServiceModel from '@/api/BaseServiceModel';
import { PartialReceiptStatus } from '@/constants/PartialReceiptStatus';
import {
ApiResponse,
Container, ReceiptPayload,
Container,
ReceiptPayload,
ReceiptResponse,
ReceivingItemPayload,
ShipmentItem,
Expand Down Expand Up @@ -52,7 +53,7 @@ class ReceivingService extends BaseServiceModel {
try {
await this.changeReceiptStatus(id, PartialReceiptStatus.ROLLBACK);
} catch (error) {
throw new Error('Problem rolling back partial receipt')
throw new Error('Problem rolling back partial receipt');
}
}

Expand All @@ -78,17 +79,21 @@ class ReceivingService extends BaseServiceModel {
*/
async updateReceivingItems(
id: string,
items: ReceivingItemPayload[],
items: ReceivingItemPayload[]
): Promise<void> {
try {
const receipt = await this.getReceipt(id);
const shipmentItemsToUpdate = this.extractShipmentItemIds(items);
const containers = this.buildUpdatedContainers(receipt.data.containers, items, shipmentItemsToUpdate);
const containers = this.buildUpdatedContainers(
receipt.data.containers,
items,
shipmentItemsToUpdate
);
const payload: ReceiptPayload = {
...receipt.data,
containers: containers,
recipient: receipt?.data?.recipient?.id,
}
};
await this.request.post(`./api/partialReceiving/${id}`, {
data: payload,
});
Expand All @@ -97,6 +102,28 @@ class ReceivingService extends BaseServiceModel {
}
}

/*
As arguments take the shipment id and the receipt object. It should be called before updating items
to create a receiving bin.
*/
async createReceivingBin(
id: string,
receipt: ReceiptResponse,
): Promise<void> {
try {
const payload: ReceiptPayload = {
...receipt,
containers: this.createEmptyContainers(receipt?.containers),
recipient: receipt.recipient?.id,
};
await this.request.post(`./api/partialReceiving/${id}`, {
data: payload,
});
} catch (error) {
throw new Error('Problem creating receiving bin');
}
}

/*
As arguments take the shipment id, id of receipt item that needs to be split,
new lines are an array filled with new items, including the original one, that is split.
Expand Down Expand Up @@ -133,14 +160,19 @@ class ReceivingService extends BaseServiceModel {
async splitReceivingLine(
id: string,
originalReceiptItemId: string,
newLines: ReceivingItemPayload[],
newLines: ReceivingItemPayload[]
) {
try {
const receipt = await this.getReceipt(id);

const originalShipmentItem = this.findOriginalShipmentItem(receipt.data.containers, originalReceiptItemId);
const originalShipmentItem = this.findOriginalShipmentItem(
receipt.data.containers,
originalReceiptItemId
);
if (!originalShipmentItem) {
throw new Error(`Original shipment item with ID ${originalReceiptItemId} not found`);
throw new Error(
`Original shipment item with ID ${originalReceiptItemId} not found`
);
}

this.validateQuantity(originalShipmentItem.quantityShipped, newLines);
Expand All @@ -159,17 +191,40 @@ class ReceivingService extends BaseServiceModel {
}
}

private findOriginalShipmentItem(containers: Container[], receiptItemId: string): ShipmentItem | undefined {
return _.flatten(containers.map(c => c.shipmentItems))
.find(item => item.receiptItemId === receiptItemId);
private createEmptyContainers(
containers: Container[]
): Container[] {
return containers?.map((container) => {
return {
...container,
shipmentItems: container?.shipmentItems?.map(shipmentItem => ({
...shipmentItem,
quantityReceiving: 0,
}))
}
})
}

private validateQuantity(originalQuantityShipped: number | undefined, newLines: ReceivingItemPayload[]) {
private findOriginalShipmentItem(
containers: Container[],
receiptItemId: string
): ShipmentItem | undefined {
return _.flatten(containers.map((c) => c.shipmentItems)).find(
(item) => item.receiptItemId === receiptItemId
);
}

private validateQuantity(
originalQuantityShipped: number | undefined,
newLines: ReceivingItemPayload[]
) {
const sumOfQuantityShipped = _.sumBy(newLines, 'quantityShipped');
const originalQty = originalQuantityShipped || 0;

if (originalQty < sumOfQuantityShipped) {
throw new Error('Sum of quantity shipped is greater than the original quantity shipped');
throw new Error(
'Sum of quantity shipped is greater than the original quantity shipped'
);
}
}

Expand All @@ -178,41 +233,45 @@ class ReceivingService extends BaseServiceModel {
originalReceiptItemId: string,
newLines: ReceivingItemPayload[]
): UnflattenContainer[] {
const splittedItem = newLines.find(line => line.receiptItemId === originalReceiptItemId);
const linesToSave = newLines.filter(line => !line.receiptItemId);
const splittedItem = newLines.find(
(line) => line.receiptItemId === originalReceiptItemId
);
const linesToSave = newLines.filter((line) => !line.receiptItemId);

return containers.map(container => {
return containers.map((container) => {
const updatedShipmentItems = _.flatten(
container.shipmentItems.map((shipmentItem: ShipmentItem) => {
if (shipmentItem.receiptItemId === originalReceiptItemId) {
return [
{ ...shipmentItem, ...splittedItem },
...linesToSave
];
return [{ ...shipmentItem, ...splittedItem }, ...linesToSave];
}
return shipmentItem;
})
);

return unflatten({ ...container, shipmentItems: updatedShipmentItems }) as UnflattenContainer;
return unflatten({
...container,
shipmentItems: updatedShipmentItems,
}) as UnflattenContainer;
});
}

private buildPayload(receiptData: ReceiptResponse, containers: UnflattenContainer[]): ReceiptPayload {
private buildPayload(
receiptData: ReceiptResponse,
containers: UnflattenContainer[]
): ReceiptPayload {
return {
...receiptData,
containers,
recipient: receiptData.recipient.id
recipient: receiptData.recipient.id,
};
}

private async saveSplitLines(id: string, payload: ReceiptPayload) {
await this.request.post(`./api/partialReceiving/${id}`, {
data: payload
data: payload,
});
}


private async changeReceiptStatus(
id: string,
status: PartialReceiptStatus
Expand All @@ -233,42 +292,47 @@ class ReceivingService extends BaseServiceModel {
private buildUpdatedContainers(
containers: Container[],
items: ReceivingItemPayload[],
shipmentItemsToUpdate: string[],
shipmentItemsToUpdate: string[]
): UnflattenContainer[] {
return containers.map((container) => {
return unflatten({
...container,
shipmentItems: container.shipmentItems.map((shipmentItem: ShipmentItem) => {
if (shipmentItemsToUpdate.includes(shipmentItem.shipmentItemId)) {
const updatedItem = this.findItemToUpdate(items, shipmentItem.shipmentItemId);
return this.mergeShipmentItem(shipmentItem, updatedItem);
}
shipmentItems: container.shipmentItems.map(
(shipmentItem: ShipmentItem) => {
if (shipmentItemsToUpdate.includes(shipmentItem.shipmentItemId)) {
const updatedItem = this.findItemToUpdate(
items,
shipmentItem.shipmentItemId
);
return this.mergeShipmentItem(shipmentItem, updatedItem);
}

return shipmentItem;
}),
return shipmentItem;
}
),
}) as UnflattenContainer;
});
}

private findItemToUpdate(
items: ReceivingItemPayload[],
shipmentItemId: string,
shipmentItemId: string
): ReceivingItemPayload | undefined {
return items.find((item) => item.shipmentItemId === shipmentItemId);
}

private mergeShipmentItem(
original: ShipmentItem,
update?: ReceivingItemPayload,
update?: ReceivingItemPayload
): ShipmentItem {
return {
...original,
binLocationId: update?.binLocationId ?? original.binLocationId,
quantityReceiving: update?.quantityReceiving ?? original.quantityReceiving,
'binLocation.name': update?.binLocationName ?? original.binLocationName,
quantityReceiving:
update?.quantityReceiving ?? original.quantityReceiving,
comment: update?.comment ?? original.comment,
};
}

}

export default ReceivingService;
16 changes: 16 additions & 0 deletions src/components/Navbar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ class Navbar extends BasePageModel {
return this.navbar.getByRole('menuitem', { name: 'Edit Profile' });
}

get refreshCachesButton() {
return this.navbar.getByRole('menuitem', { name: 'Refresh Caches' });
}

// Nav Items
get dashboard() {
return this.getNavItem('Dashboard');
Expand Down Expand Up @@ -79,6 +83,18 @@ class Navbar extends BasePageModel {
get listProducts() {
return this.getNavItem('List Products');
}

get inbound() {
return this.getNavItem('Inbound');
}

get createPutaway() {
return this.getNavItem('Create Putaway');
}

get transactions() {
return this.getNavItem('Transactions');
}
}

export default Navbar;
16 changes: 15 additions & 1 deletion src/config/AppConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export enum LOCATION_KEY {
DEPOT = 'depot',
WARD = 'ward',
NO_PICK_AND_PUTAWAY_STOCK = 'noPickAndPutawayStockDepot',
BIN_LOCATION = 'internalLocation',
}

export enum PRODUCT_KEY {
Expand Down Expand Up @@ -256,6 +257,19 @@ class AppConfig {
required: false,
type: LocationTypeCode.DEPOT,
}),

internalLocation: new LocationConfig({
id: env.get('LOCATION_INTERNAL').asString(),
key: LOCATION_KEY.BIN_LOCATION,
name: this.uniqueIdentifier.generateUniqueString('bin-location'),
requiredActivityCodes: new Set([
ActivityCode.PICK_STOCK,
ActivityCode.PUTAWAY_STOCK,
]),
required: false,
type: LocationTypeCode.BIN_LOCATION,
parentLocation: env.get('LOCATION_MAIN').required().asString(),
}),
};

this.products = {
Expand Down Expand Up @@ -292,7 +306,7 @@ class AppConfig {
key: PRODUCT_KEY.FIVE,
name: this.uniqueIdentifier.generateUniqueString('aa-product-five'),
//'aa' part was added to improve visibility of ordering products alphabetically
quantity: 160,
quantity: 0,
required: false,
}),
};
Expand Down
4 changes: 4 additions & 0 deletions src/config/LocationConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ type LocationConfigProps = {
key: string;
requiredActivityCodes: Set<ActivityCode>;
type: LocationTypeCode;
parentLocation?: string;
} & (
| {
id: string;
Expand All @@ -29,6 +30,7 @@ class LocationConfig {
type: LocationTypeCode;
required: boolean;
key: string;
parentLocation?: string;

constructor({
key,
Expand All @@ -37,13 +39,15 @@ class LocationConfig {
requiredActivityCodes,
type,
required,
parentLocation,
}: LocationConfigProps) {
this.id = id || '';
this.name = name || '';
this.requiredActivityCodes = requiredActivityCodes;
this.type = type;
this.required = required ?? false;
this.key = key;
this.parentLocation = parentLocation || '';
}

/** Should create a new location for testing
Expand Down
Loading