homemanager-be/src/app-storage/app-storage.service.ts

463 lines
13 KiB
TypeScript

import {
Injectable,
NotFoundException,
UnauthorizedException,
} from '@nestjs/common';
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';
import { StoredItemTransaction } from 'src/objects/storage/entities/item-transaction.entity';
import { Item } from 'src/objects/storage/entities/item.entity';
import { StorageSet } from 'src/objects/storage/entities/storage-set.entity';
import { Storage } from 'src/objects/storage/entities/storage.entity';
import { StoredItem } from 'src/objects/storage/entities/stored-item.entity';
import { TransactionType } from 'src/objects/storage/enums/transaction-type.enum';
import { StorageService } from 'src/objects/storage/storage.service';
import { User } from 'src/objects/user/user.entity';
import {
StorageAddExistingItemRequestDto,
StorageAddItemRequestDto,
StorageItemUpdateRequestDto,
StorageStoredItemRequestDto,
StorageStoredItemTransactionRequestDto,
StorageStoredItemTransactionUpdateRequestDto,
StorageStoredItemUpdateRequestDto,
} from './dto/storage-add-item-request.dto';
import { StorageItemRequestQueryDto } from './dto/storage-item-request.dto';
import {
StorageItemResponseDto,
StorageItemSearchResponseDto,
StorageStoredItemResponseDto,
StorageTransactionResponseDto,
StorageTransactionWithActorResponseDto,
} from './dto/storage-item-response.dto';
import {
StorageCreateRequestDto,
StorageUpdateRequestDto,
} from './dto/storage-request.dto';
import {
StorageActorResponse,
StorageResponseDto,
StorageResponseWithItemCountDto,
} 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,
) {}
async getStoragesInRoom(roomId: number, includeWithSets = false) {
const storages = await this.storageService.getStoragesInRoom(
roomId,
includeWithSets,
);
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];
return this.formatStorageSetNoItems(set);
}
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);
}
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;
}
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),
);
}
async createStoredItem(
user: User,
item: Item,
storage: Storage,
transactionInfo: StorageStoredItemTransactionRequestDto,
additionalInfo?: StorageStoredItemRequestDto,
) {
const building = await this.buildingService.getStorageBuilding(storage.id);
if (!building)
throw new NotFoundException('Building for storage was not found');
// Create stored item
let storedItem = new StoredItem();
storedItem.addedBy = user;
storedItem.item = item;
storedItem.storage = storage;
storedItem.building = building;
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,
);
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);
}
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),
);
}
async getStoredItemDetails(storage: Storage, storedItemId: number) {
const storedItem = await this.storageService.getStoredItemByStorageAndId(
storage,
storedItemId,
['item', 'addedBy'],
);
if (!storedItem) {
throw new NotFoundException('Stored item not found');
}
return this.formatStoredItem(storedItem);
}
async getStorageWithItems(storage: Storage) {
const withItemCount = await this.storageService.getStorageWithItemCount(
storage.id,
);
return this.formatStorageNoItems(
withItemCount,
) as StorageResponseWithItemCountDto;
}
private formatActor(input: User): StorageActorResponse {
return pick(input, ['name', 'sub', 'picture', 'color']);
}
private formatStorageNoItems(storage: Storage): StorageResponseDto {
return {
...omit(storage, ['room', 'items']),
addedBy: storage.addedBy && this.formatActor(storage.addedBy),
};
}
private formatStorageWithItems(storage: Storage): StorageResponseDto {
return {
...omit(storage, ['room', 'set']),
items: !!storage.items?.length
? storage.items.map((item) => this.formatStoredItem(item))
: null,
addedBy: storage.addedBy && this.formatActor(storage.addedBy),
};
}
private formatItem(item: Item): StorageItemResponseDto {
return {
...omit(item, ['instances', 'addedBy']),
addedBy: item.addedBy && this.formatActor(item.addedBy),
};
}
private formatTransaction(
transaction: StoredItemTransaction,
): StorageTransactionResponseDto {
return omit(transaction, ['storedItem', 'actor']);
}
private formatTransactionWithActor(
transaction: StoredItemTransaction,
): StorageTransactionWithActorResponseDto {
return {
...omit(transaction, ['storedItem']),
actor: transaction.actor && this.formatActor(transaction.actor),
};
}
private formatStoredItem(
storedItem: StoredItem,
fullSet = false,
): StorageStoredItemResponseDto {
return {
...(fullSet ? storedItem : omit(storedItem, ['storage', 'building'])),
transactions: !!storedItem.transactions?.length
? storedItem.transactions.map((transaction) =>
this.formatTransaction(transaction),
)
: null,
item: storedItem.item ? this.formatItem(storedItem.item) : null,
addedBy: storedItem.addedBy ? this.formatActor(storedItem.addedBy) : null,
};
}
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))
: [],
};
}
}