2023-01-15 06:42:40 +00:00
|
|
|
import {
|
|
|
|
Injectable,
|
|
|
|
NotFoundException,
|
|
|
|
UnauthorizedException,
|
|
|
|
} from '@nestjs/common';
|
2023-01-12 20:24:09 +00:00
|
|
|
import omit from 'lodash.omit';
|
|
|
|
import pick from 'lodash.pick';
|
|
|
|
import { BuildingService } from 'src/objects/building/building.service';
|
|
|
|
import { Room } from 'src/objects/building/entities/room.entity';
|
2023-01-13 19:42:21 +00:00
|
|
|
import { StoredItemTransaction } from 'src/objects/storage/entities/item-transaction.entity';
|
|
|
|
import { Item } from 'src/objects/storage/entities/item.entity';
|
2023-01-12 20:24:09 +00:00
|
|
|
import { StorageSet } from 'src/objects/storage/entities/storage-set.entity';
|
|
|
|
import { Storage } from 'src/objects/storage/entities/storage.entity';
|
2023-01-13 19:42:21 +00:00
|
|
|
import { StoredItem } from 'src/objects/storage/entities/stored-item.entity';
|
|
|
|
import { TransactionType } from 'src/objects/storage/enums/transaction-type.enum';
|
2023-01-12 20:24:09 +00:00
|
|
|
import { StorageService } from 'src/objects/storage/storage.service';
|
|
|
|
import { User } from 'src/objects/user/user.entity';
|
2023-01-13 19:42:21 +00:00
|
|
|
import {
|
|
|
|
StorageAddExistingItemRequestDto,
|
|
|
|
StorageAddItemRequestDto,
|
|
|
|
StorageItemUpdateRequestDto,
|
|
|
|
StorageStoredItemRequestDto,
|
|
|
|
StorageStoredItemTransactionRequestDto,
|
2023-01-15 06:42:40 +00:00
|
|
|
StorageStoredItemTransactionUpdateRequestDto,
|
2023-01-13 19:42:21 +00:00
|
|
|
StorageStoredItemUpdateRequestDto,
|
|
|
|
} from './dto/storage-add-item-request.dto';
|
2023-01-12 21:04:42 +00:00
|
|
|
import { StorageItemRequestQueryDto } from './dto/storage-item-request.dto';
|
2023-01-13 19:42:21 +00:00
|
|
|
import {
|
|
|
|
StorageItemResponseDto,
|
|
|
|
StorageItemSearchResponseDto,
|
|
|
|
StorageStoredItemResponseDto,
|
|
|
|
StorageTransactionResponseDto,
|
2023-01-15 06:42:40 +00:00
|
|
|
StorageTransactionWithActorResponseDto,
|
2023-01-13 19:42:21 +00:00
|
|
|
} from './dto/storage-item-response.dto';
|
2023-01-12 20:24:09 +00:00
|
|
|
import {
|
|
|
|
StorageCreateRequestDto,
|
|
|
|
StorageUpdateRequestDto,
|
|
|
|
} from './dto/storage-request.dto';
|
|
|
|
import {
|
|
|
|
StorageActorResponse,
|
|
|
|
StorageResponseDto,
|
|
|
|
} from './dto/storage-response.dto';
|
|
|
|
import {
|
|
|
|
StorageSetCreateRequestDto,
|
|
|
|
StorageSetUpdateRequestDto,
|
|
|
|
} from './dto/storage-set-request.dto';
|
|
|
|
import { StorageSetResponseDto } from './dto/storage-set-response.dto';
|
|
|
|
|
|
|
|
@Injectable()
|
|
|
|
export class AppStorageService {
|
|
|
|
constructor(
|
|
|
|
private readonly buildingService: BuildingService,
|
|
|
|
private readonly storageService: StorageService,
|
|
|
|
) {}
|
|
|
|
|
2023-01-18 20:48:58 +00:00
|
|
|
async getStoragesInRoom(roomId: number, includeWithSets = false) {
|
|
|
|
const storages = await this.storageService.getStoragesInRoom(
|
|
|
|
roomId,
|
|
|
|
includeWithSets,
|
|
|
|
);
|
2023-01-12 20:24:09 +00:00
|
|
|
return storages.map((storage) => this.formatStorageNoItems(storage));
|
|
|
|
}
|
|
|
|
|
|
|
|
async getStorageSetsInRoom(roomId: number) {
|
|
|
|
const sets = await this.storageService.getStorageSetsInRoom(roomId);
|
|
|
|
return sets.map((set) => this.formatStorageSetNoItems(set));
|
|
|
|
}
|
|
|
|
|
|
|
|
async createStorage(user: User, room: Room, body: StorageCreateRequestDto) {
|
|
|
|
// TODO: validate color and location
|
|
|
|
const newStorage = await this.storageService.saveStorage({
|
|
|
|
...body,
|
|
|
|
addedBy: user,
|
|
|
|
room,
|
|
|
|
});
|
|
|
|
|
|
|
|
return this.formatStorageNoItems(newStorage);
|
|
|
|
}
|
|
|
|
|
|
|
|
async createStorageSet(
|
|
|
|
user: User,
|
|
|
|
room: Room,
|
|
|
|
body: StorageSetCreateRequestDto,
|
|
|
|
) {
|
|
|
|
// TODO: validate color and location
|
|
|
|
const newStorageSet = await this.storageService.saveStorageSet({
|
|
|
|
...body,
|
|
|
|
addedBy: user,
|
|
|
|
room,
|
|
|
|
});
|
|
|
|
|
|
|
|
return this.formatStorageSetNoItems(newStorageSet);
|
|
|
|
}
|
|
|
|
|
|
|
|
async updateStorage(storage: Storage, body: StorageUpdateRequestDto) {
|
|
|
|
Object.assign(storage, body);
|
|
|
|
await this.storageService.saveStorage(storage);
|
|
|
|
return this.formatStorageNoItems(storage);
|
|
|
|
}
|
|
|
|
|
|
|
|
async updateStorageSet(
|
|
|
|
storageSet: StorageSet,
|
|
|
|
body: StorageSetUpdateRequestDto,
|
|
|
|
) {
|
|
|
|
Object.assign(storageSet, body);
|
|
|
|
await this.storageService.saveStorageSet(storageSet);
|
|
|
|
return this.formatStorageSetNoItems(storageSet);
|
|
|
|
}
|
|
|
|
|
|
|
|
async moveStorage(set: StorageSet, storage: Storage) {
|
|
|
|
storage.set = set;
|
|
|
|
|
|
|
|
await this.storageService.saveStorage(storage);
|
|
|
|
|
|
|
|
set.storages = [...(set.storages || []), storage];
|
|
|
|
|
2023-01-26 18:30:39 +00:00
|
|
|
// Remove circular set reference
|
|
|
|
const respondWith = {
|
|
|
|
...set,
|
|
|
|
storages: set.storages.map((storage) => omit(storage, ['set'])),
|
|
|
|
};
|
|
|
|
|
|
|
|
return this.formatStorageSetNoItems(respondWith);
|
2023-01-12 20:24:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async removeFromSet(set: StorageSet, storage: Storage) {
|
|
|
|
storage.set = null;
|
|
|
|
await this.storageService.saveStorage(storage);
|
|
|
|
set.storages = (set.storages || []).filter(
|
|
|
|
(entry) => entry.id !== storage.id,
|
|
|
|
);
|
|
|
|
return this.formatStorageSetNoItems(set);
|
|
|
|
}
|
|
|
|
|
2023-01-12 21:04:42 +00:00
|
|
|
async searchForItems(user: User, query: StorageItemRequestQueryDto) {
|
|
|
|
let responses: StorageItemSearchResponseDto[];
|
|
|
|
if (query.searchTerm) {
|
|
|
|
responses = await this.storageService.searchForItem(
|
|
|
|
query.searchTerm,
|
|
|
|
user.sub,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
responses = await this.storageService.searchForItemByBarcode(
|
|
|
|
query.barcode,
|
|
|
|
user.sub,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
return responses;
|
|
|
|
}
|
|
|
|
|
2023-01-24 19:03:43 +00:00
|
|
|
async getExpiringOrExpiredItemsInBuilding(buildingId: number) {
|
|
|
|
const expiringSoon =
|
|
|
|
await this.storageService.getExpiredOrExpiringSoonInBuilding(buildingId);
|
|
|
|
return expiringSoon.map((storedItem) =>
|
|
|
|
this.formatStoredItem(storedItem, true),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async getExpiringOrExpiredItems(user: User) {
|
|
|
|
const expiringSoon =
|
|
|
|
await this.storageService.getExpiredOrExpiringSoonForSub(user.sub);
|
|
|
|
return expiringSoon.map((storedItem) =>
|
|
|
|
this.formatStoredItem(storedItem, true),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-01-24 20:37:45 +00:00
|
|
|
async searchForStoredItems(
|
|
|
|
search: StorageItemRequestQueryDto,
|
|
|
|
user: User,
|
|
|
|
buildingId?: number,
|
|
|
|
) {
|
|
|
|
const foundItems = await this.storageService.searchForStoredItem(
|
|
|
|
search,
|
|
|
|
user.sub,
|
|
|
|
buildingId,
|
|
|
|
);
|
|
|
|
return foundItems.map((storedItem) =>
|
|
|
|
this.formatStoredItem(storedItem, true),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2023-01-13 19:42:21 +00:00
|
|
|
async createStoredItem(
|
|
|
|
user: User,
|
|
|
|
item: Item,
|
|
|
|
storage: Storage,
|
|
|
|
transactionInfo: StorageStoredItemTransactionRequestDto,
|
|
|
|
additionalInfo?: StorageStoredItemRequestDto,
|
|
|
|
) {
|
2023-01-24 19:03:43 +00:00
|
|
|
const building = await this.buildingService.getStorageBuilding(storage.id);
|
|
|
|
if (!building)
|
|
|
|
throw new NotFoundException('Building for storage was not found');
|
2023-01-13 19:42:21 +00:00
|
|
|
// Create stored item
|
|
|
|
let storedItem = new StoredItem();
|
|
|
|
storedItem.addedBy = user;
|
|
|
|
storedItem.item = item;
|
|
|
|
storedItem.storage = storage;
|
2023-01-24 19:03:43 +00:00
|
|
|
storedItem.building = building;
|
2023-01-13 19:42:21 +00:00
|
|
|
additionalInfo && Object.assign(storedItem, additionalInfo);
|
|
|
|
|
|
|
|
storedItem = await this.storageService.saveStoredItem(storedItem);
|
|
|
|
|
|
|
|
// Create transaction
|
|
|
|
let transaction = new StoredItemTransaction();
|
|
|
|
transaction.actor = user;
|
|
|
|
transaction.storedItem = storedItem;
|
|
|
|
Object.assign(transaction, transactionInfo);
|
|
|
|
|
|
|
|
transaction = await this.storageService.saveStoredItemTransaction(
|
|
|
|
transaction,
|
|
|
|
);
|
|
|
|
|
|
|
|
storedItem.transactions = [transaction];
|
|
|
|
|
|
|
|
return new StorageStoredItemResponseDto(this.formatStoredItem(storedItem));
|
|
|
|
}
|
|
|
|
|
|
|
|
async createNewItem(
|
|
|
|
user: User,
|
|
|
|
storage: Storage,
|
|
|
|
body: StorageAddItemRequestDto,
|
|
|
|
) {
|
|
|
|
// Create item
|
|
|
|
let item = new Item();
|
|
|
|
item.addedBy = user;
|
|
|
|
Object.assign(
|
|
|
|
item,
|
|
|
|
pick(body, [
|
|
|
|
'displayName',
|
|
|
|
'type',
|
|
|
|
'barcode',
|
|
|
|
'consumable',
|
|
|
|
'image',
|
|
|
|
'weight',
|
|
|
|
'url',
|
|
|
|
'notes',
|
|
|
|
'public',
|
|
|
|
]),
|
|
|
|
);
|
|
|
|
|
|
|
|
item = await this.storageService.saveItem(item);
|
|
|
|
|
|
|
|
return this.createStoredItem(
|
|
|
|
user,
|
|
|
|
item,
|
|
|
|
storage,
|
|
|
|
body.transactionInfo,
|
|
|
|
body.additionalInfo,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async addExistingItemToStorage(
|
|
|
|
user: User,
|
|
|
|
storage: Storage,
|
|
|
|
itemId: number,
|
|
|
|
body: StorageAddExistingItemRequestDto,
|
|
|
|
) {
|
|
|
|
const item = await this.storageService.getItemByIdBySub(itemId, user.sub);
|
|
|
|
if (!item) {
|
|
|
|
throw new NotFoundException('Item not found');
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.createStoredItem(
|
|
|
|
user,
|
|
|
|
item,
|
|
|
|
storage,
|
|
|
|
body.transactionInfo,
|
|
|
|
body.additionalInfo,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async updateOwnedItem(
|
|
|
|
user: User,
|
|
|
|
itemId: number,
|
|
|
|
body: StorageItemUpdateRequestDto,
|
|
|
|
) {
|
|
|
|
const item = await this.storageService.getItemByIdOwnedBySub(
|
|
|
|
itemId,
|
|
|
|
user.sub,
|
|
|
|
);
|
|
|
|
if (!item) {
|
|
|
|
throw new NotFoundException('Item not found');
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.assign(item, body);
|
|
|
|
|
|
|
|
await this.storageService.saveItem(item);
|
|
|
|
|
|
|
|
return this.formatItem(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
async updateStoredItemDetails(
|
|
|
|
storage: Storage,
|
|
|
|
storedItemId: number,
|
|
|
|
body: StorageStoredItemUpdateRequestDto,
|
|
|
|
) {
|
|
|
|
const storedItem = await this.storageService.getStoredItemByStorageAndId(
|
|
|
|
storage,
|
|
|
|
storedItemId,
|
|
|
|
);
|
|
|
|
if (!storedItem) {
|
|
|
|
throw new NotFoundException('Stored item not found');
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.assign(storedItem, body);
|
|
|
|
|
|
|
|
await this.storageService.saveStoredItem(storedItem);
|
|
|
|
|
|
|
|
return this.formatStoredItem(storedItem);
|
|
|
|
}
|
|
|
|
|
|
|
|
async createStoredItemTransaction(
|
|
|
|
user: User,
|
|
|
|
storage: Storage,
|
|
|
|
storedItemId: number,
|
|
|
|
body: StorageStoredItemTransactionRequestDto,
|
|
|
|
) {
|
|
|
|
const storedItem = await this.storageService.getStoredItemByStorageAndId(
|
|
|
|
storage,
|
|
|
|
storedItemId,
|
|
|
|
);
|
2023-01-15 06:42:40 +00:00
|
|
|
|
2023-01-13 19:42:21 +00:00
|
|
|
if (!storedItem) {
|
|
|
|
throw new NotFoundException('Stored item not found');
|
|
|
|
}
|
|
|
|
|
|
|
|
const transaction = await this.storageService.saveStoredItemTransaction({
|
|
|
|
...body,
|
|
|
|
actor: user,
|
|
|
|
storedItem,
|
|
|
|
});
|
|
|
|
|
|
|
|
if (
|
|
|
|
[
|
|
|
|
TransactionType.SOLD,
|
|
|
|
TransactionType.DESTROYED,
|
|
|
|
TransactionType.BINNED,
|
|
|
|
].includes(body.type)
|
|
|
|
) {
|
|
|
|
storedItem.consumedAt = new Date();
|
|
|
|
await this.storageService.saveStoredItem(storedItem);
|
|
|
|
}
|
|
|
|
|
2023-01-15 06:42:40 +00:00
|
|
|
return this.formatTransaction(transaction);
|
|
|
|
}
|
|
|
|
|
|
|
|
async updateStoredItemTransaction(
|
|
|
|
user: User,
|
|
|
|
storedItemId: number,
|
|
|
|
transactionId: number,
|
|
|
|
body: StorageStoredItemTransactionUpdateRequestDto,
|
|
|
|
) {
|
|
|
|
const storedItemTransaction =
|
|
|
|
await this.storageService.getStoredItemTransactionById(
|
|
|
|
storedItemId,
|
|
|
|
transactionId,
|
|
|
|
['actor'],
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!storedItemTransaction) {
|
|
|
|
throw new NotFoundException('Stored item not found');
|
|
|
|
}
|
|
|
|
|
|
|
|
if (storedItemTransaction.actor.sub === user.sub) {
|
|
|
|
throw new UnauthorizedException(
|
|
|
|
'You can only update your own transactions',
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.assign(storedItemTransaction, body);
|
|
|
|
await this.storageService.saveStoredItemTransaction(storedItemTransaction);
|
|
|
|
|
|
|
|
return this.formatTransactionWithActor(storedItemTransaction);
|
|
|
|
}
|
|
|
|
|
|
|
|
async getStoredItemTransaction(storage: Storage, storedItemId: number) {
|
|
|
|
const storedItem = await this.storageService.getStoredItemByStorageAndId(
|
|
|
|
storage,
|
|
|
|
storedItemId,
|
|
|
|
['transactions', 'transactions.actor'],
|
|
|
|
);
|
|
|
|
|
|
|
|
if (!storedItem) {
|
|
|
|
throw new NotFoundException('Stored item not found');
|
|
|
|
}
|
|
|
|
|
|
|
|
return storedItem.transactions.map((transaction) =>
|
|
|
|
this.formatTransactionWithActor(transaction),
|
|
|
|
);
|
2023-01-13 19:42:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async getStoredItemDetails(storage: Storage, storedItemId: number) {
|
|
|
|
const storedItem = await this.storageService.getStoredItemByStorageAndId(
|
|
|
|
storage,
|
|
|
|
storedItemId,
|
2023-01-15 06:42:40 +00:00
|
|
|
['item', 'addedBy'],
|
2023-01-13 19:42:21 +00:00
|
|
|
);
|
|
|
|
if (!storedItem) {
|
|
|
|
throw new NotFoundException('Stored item not found');
|
|
|
|
}
|
|
|
|
|
|
|
|
return this.formatStoredItem(storedItem);
|
|
|
|
}
|
|
|
|
|
|
|
|
async getStorageWithItems(storage: Storage) {
|
2023-01-25 17:01:34 +00:00
|
|
|
const withItemCount = await this.storageService.getStorageById(storage.id, [
|
|
|
|
'items',
|
|
|
|
'items.item',
|
|
|
|
'items.addedBy',
|
|
|
|
]);
|
2023-01-13 19:42:21 +00:00
|
|
|
|
2023-01-25 17:01:34 +00:00
|
|
|
return this.formatStorageWithItems(withItemCount) as StorageResponseDto;
|
2023-01-13 19:42:21 +00:00
|
|
|
}
|
|
|
|
|
2023-01-15 06:42:40 +00:00
|
|
|
private formatActor(input: User): StorageActorResponse {
|
|
|
|
return pick(input, ['name', 'sub', 'picture', 'color']);
|
2023-01-12 20:24:09 +00:00
|
|
|
}
|
|
|
|
|
2023-01-15 06:42:40 +00:00
|
|
|
private formatStorageNoItems(storage: Storage): StorageResponseDto {
|
2023-01-12 20:24:09 +00:00
|
|
|
return {
|
2023-01-18 20:48:58 +00:00
|
|
|
...omit(storage, ['room', 'items']),
|
2023-01-12 20:24:09 +00:00
|
|
|
addedBy: storage.addedBy && this.formatActor(storage.addedBy),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-01-15 06:42:40 +00:00
|
|
|
private formatStorageWithItems(storage: Storage): StorageResponseDto {
|
2023-01-13 19:42:21 +00:00
|
|
|
return {
|
|
|
|
...omit(storage, ['room', 'set']),
|
|
|
|
items: !!storage.items?.length
|
|
|
|
? storage.items.map((item) => this.formatStoredItem(item))
|
2023-01-26 18:30:39 +00:00
|
|
|
: [],
|
2023-01-13 19:42:21 +00:00
|
|
|
addedBy: storage.addedBy && this.formatActor(storage.addedBy),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-01-15 06:42:40 +00:00
|
|
|
private formatItem(item: Item): StorageItemResponseDto {
|
2023-01-13 19:42:21 +00:00
|
|
|
return {
|
|
|
|
...omit(item, ['instances', 'addedBy']),
|
|
|
|
addedBy: item.addedBy && this.formatActor(item.addedBy),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-01-15 06:42:40 +00:00
|
|
|
private formatTransaction(
|
2023-01-13 19:42:21 +00:00
|
|
|
transaction: StoredItemTransaction,
|
|
|
|
): StorageTransactionResponseDto {
|
|
|
|
return omit(transaction, ['storedItem', 'actor']);
|
|
|
|
}
|
|
|
|
|
2023-01-15 06:42:40 +00:00
|
|
|
private formatTransactionWithActor(
|
|
|
|
transaction: StoredItemTransaction,
|
|
|
|
): StorageTransactionWithActorResponseDto {
|
|
|
|
return {
|
|
|
|
...omit(transaction, ['storedItem']),
|
|
|
|
actor: transaction.actor && this.formatActor(transaction.actor),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
private formatStoredItem(
|
|
|
|
storedItem: StoredItem,
|
2023-01-24 19:03:43 +00:00
|
|
|
fullSet = false,
|
2023-01-15 06:42:40 +00:00
|
|
|
): StorageStoredItemResponseDto {
|
2023-01-13 19:42:21 +00:00
|
|
|
return {
|
2023-01-24 19:03:43 +00:00
|
|
|
...(fullSet ? storedItem : omit(storedItem, ['storage', 'building'])),
|
2023-01-13 19:42:21 +00:00
|
|
|
transactions: !!storedItem.transactions?.length
|
|
|
|
? storedItem.transactions.map((transaction) =>
|
2023-01-15 06:42:40 +00:00
|
|
|
this.formatTransaction(transaction),
|
2023-01-13 19:42:21 +00:00
|
|
|
)
|
|
|
|
: null,
|
|
|
|
item: storedItem.item ? this.formatItem(storedItem.item) : null,
|
|
|
|
addedBy: storedItem.addedBy ? this.formatActor(storedItem.addedBy) : null,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2023-01-12 20:24:09 +00:00
|
|
|
formatStorageSetNoItems(set: StorageSet): StorageSetResponseDto {
|
|
|
|
return {
|
|
|
|
...omit(set, ['room']),
|
|
|
|
addedBy: set.addedBy && this.formatActor(set.addedBy),
|
|
|
|
storages: set.storages?.length
|
|
|
|
? set.storages.map((storage) => this.formatStorageNoItems(storage))
|
|
|
|
: [],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|