From a103f728b5fd32b05ece4cb7586553e21a021c31 Mon Sep 17 00:00:00 2001 From: akbarsaputrait Date: Fri, 20 Sep 2024 01:06:42 +0700 Subject: [PATCH] improve product history --- src/app/customer/order/order.controller.ts | 1 + .../restaurant/order/detail.controller.ts | 2 ++ .../restaurant/product/detail.controller.ts | 24 +++++++++++++- .../restaurant/stock/stock.controller.ts | 12 ++++++- .../restaurant/order/detail.controller.ts | 2 ++ .../restaurant/stock/stock.controller.ts | 31 ++++++++++++++----- src/database/entities/core/customer.entity.ts | 4 +++ .../entities/owner/product-history.entity.ts | 3 ++ .../migrations/1726768379070-history-actor.ts | 13 ++++++++ .../subscribers/product-stock.subscriber.ts | 1 + 10 files changed, 84 insertions(+), 9 deletions(-) create mode 100644 src/database/migrations/1726768379070-history-actor.ts diff --git a/src/app/customer/order/order.controller.ts b/src/app/customer/order/order.controller.ts index 9b178fa..918722d 100644 --- a/src/app/customer/order/order.controller.ts +++ b/src/app/customer/order/order.controller.ts @@ -130,6 +130,7 @@ export class OrderController { for (const stock of productStocks) { stock.last_action = `Incoming Order: ${orderNumber}`; + stock.actor = customer.logName; await manager.getRepository(ProductStock).save(stock); } }); diff --git a/src/app/owner/restaurant/order/detail.controller.ts b/src/app/owner/restaurant/order/detail.controller.ts index aa27f36..f847367 100644 --- a/src/app/owner/restaurant/order/detail.controller.ts +++ b/src/app/owner/restaurant/order/detail.controller.ts @@ -83,6 +83,7 @@ export class DetailController { if (orderProduct) { stock.onhand -= stock.allocated; // Decrease Onhand Stock stock.allocated -= orderProduct.qty; // Decrease Allocated Stock + stock.sold += orderProduct.qty; stock.actor = actor ? actor.logName : 'System'; stock.last_action = `Completed Order: ${order.number}`; stocks.push(stock); @@ -163,6 +164,7 @@ export class DetailController { // Update Stock for (const stock of stocks) { stock.last_action = `${titleCase(order.status)} Order: ${order.number}`; + stock.actor = actor.logName; await manager.getRepository(ProductStock).save(stock); } diff --git a/src/app/owner/restaurant/product/detail.controller.ts b/src/app/owner/restaurant/product/detail.controller.ts index a5c5d6d..8a244a9 100644 --- a/src/app/owner/restaurant/product/detail.controller.ts +++ b/src/app/owner/restaurant/product/detail.controller.ts @@ -1,3 +1,4 @@ +import { Loc } from '@core/decorators/location.decorator'; import { Quero } from '@core/decorators/quero.decorator'; import { Rest } from '@core/decorators/restaurant.decorator'; import { OwnerAuthGuard } from '@core/guards/auth.guard'; @@ -5,18 +6,22 @@ import { OwnerGuard } from '@core/guards/owner.guard'; import { AwsService } from '@core/services/aws.service'; import { PermAct, PermOwner } from '@core/services/role.service'; import { Media } from '@db/entities/core/media.entity'; +import { Location } from '@db/entities/owner/location.entity'; +import { ProductHistory } from '@db/entities/owner/product-history.entity'; import { ProductStock } from '@db/entities/owner/product-stock.entity'; import { Product } from '@db/entities/owner/product.entity'; import { ProductTransformer } from '@db/transformers/product.transformer'; +import { RawTransformer } from '@db/transformers/raw.transformer'; import { StockTransformer } from '@db/transformers/stock.transformer'; import { ValidationException } from '@lib/exceptions/validation.exception'; import { Validator } from '@lib/helpers/validator.helper'; import { Permissions } from '@lib/rbac'; +import AppDataSource from '@lib/typeorm/datasource.typeorm'; import { BadRequestException, Body, Controller, Delete, Get, Param, Post, Put, Req, Res, UseGuards } from '@nestjs/common'; import { Not } from 'typeorm'; @Controller(':product_id') -@UseGuards(OwnerAuthGuard()) +@UseGuards(OwnerAuthGuard(), OwnerGuard) export class DetailController { constructor(private aws: AwsService) {} @@ -113,4 +118,21 @@ export class DetailController { return response.collection(stocks, StockTransformer); } + + @Get('/histories') + @Permissions(`${PermOwner.Product}@${PermAct.R}`) + async history(@Param() param, @Res() response, @Loc() location) { + const query = AppDataSource.createQueryBuilder(ProductHistory, 't1'); + query.leftJoin(Location, 't2', 't2.id = t1.location_id'); + query.where('t1.product_id = :product_id', { product_id: param.product_id }); + + if (location) { + query.andWhere('t2.id = :locId', { locId: location.id }); + } + + query.selectWithAlias(['t2.id', '_t2.name as location', 't1.action', 't1.data', 't1.actor', 't1.created_at']); + + const data = await query.search().sort().getRawPaged(); + await response.paginate(data, RawTransformer); + } } diff --git a/src/app/owner/restaurant/stock/stock.controller.ts b/src/app/owner/restaurant/stock/stock.controller.ts index 4a68941..3d3ef31 100644 --- a/src/app/owner/restaurant/stock/stock.controller.ts +++ b/src/app/owner/restaurant/stock/stock.controller.ts @@ -196,7 +196,7 @@ export class StockController { @Put('/:stock_id') @UseGuards(OwnerGuard) @Permissions(`${PermOwner.Stock}@${PermAct.U}`) - async update(@Rest() rest, @Body() body, @Res() response, @Param() param) { + async update(@Rest() rest, @Body() body, @Res() response, @Param() param, @Me() me: Owner) { const rules = { onhand: 'required|numeric|min:0', }; @@ -220,6 +220,8 @@ export class StockController { if (Number(body.onhand) <= 0) { await AppDataSource.transaction(async (manager) => { productStock.onhand = 0; + productStock.last_action = `Update Stock`; + productStock.actor = me.logName; await manager.getRepository(ProductStock).save(productStock); variant.status = VariantStatus.Unvailable; @@ -232,7 +234,15 @@ export class StockController { } else { await AppDataSource.transaction(async (manager) => { productStock.onhand = Number(body.onhand); + productStock.last_action = `Update Stock`; + productStock.actor = me.logName; await manager.getRepository(ProductStock).save(productStock); + + variant.status = VariantStatus.Available; + await manager.getRepository(ProductVariant).save(variant); + + product.status = ProductStatus.Available; + await manager.getRepository(Product).save(product); }); } diff --git a/src/app/staff/restaurant/order/detail.controller.ts b/src/app/staff/restaurant/order/detail.controller.ts index bf065a4..53cbaba 100644 --- a/src/app/staff/restaurant/order/detail.controller.ts +++ b/src/app/staff/restaurant/order/detail.controller.ts @@ -83,6 +83,7 @@ export class DetailController { if (orderProduct) { stock.onhand -= stock.allocated; // Decrease Onhand Stock stock.allocated -= orderProduct.qty; // Decrease Allocated Stock + stock.sold += orderProduct.qty; stock.actor = actor ? actor.logName : 'System'; stock.last_action = `Completed Order: ${order.number}`; stocks.push(stock); @@ -163,6 +164,7 @@ export class DetailController { // Update Stock for (const stock of stocks) { stock.last_action = `${titleCase(order.status)} Order: ${order.number}`; + stock.actor = actor.logName; await manager.getRepository(ProductStock).save(stock); } diff --git a/src/app/staff/restaurant/stock/stock.controller.ts b/src/app/staff/restaurant/stock/stock.controller.ts index ac37403..0d9e2e8 100644 --- a/src/app/staff/restaurant/stock/stock.controller.ts +++ b/src/app/staff/restaurant/stock/stock.controller.ts @@ -213,7 +213,7 @@ export class StockController { @Put('/:stock_id') @UseGuards(StaffGuard) @Permissions(`${PermStaff.Stock}@${PermAct.U}`) - async update(@Rest() rest, @Body() body, @Res() response, @Param() param) { + async update(@Rest() rest, @Body() body, @Res() response, @Param() param, @Me() me: StaffUser) { const rules = { onhand: 'required|numeric|min:0', }; @@ -234,15 +234,32 @@ export class StockController { if (variant === null) { // Set all product variants to unavailable when parent are 0 if (Number(body.onhand) <= 0) { - await ProductStock.update({ product_id: product.id }, { onhand: 0 }); - await ProductVariant.update({ product_id: product.id }, { status: VariantStatus.Unvailable }); + await AppDataSource.transaction(async (manager) => { + productStock.onhand = 0; + productStock.last_action = `Update Stock`; + productStock.actor = me.logName; + await manager.getRepository(ProductStock).save(productStock); - product.status = ProductStatus.Unvailable; - await product.save(); + variant.status = VariantStatus.Unvailable; + await manager.getRepository(ProductVariant).save(variant); + + product.status = ProductStatus.Unvailable; + await manager.getRepository(Product).save(product); + }); } } else { - productStock.onhand = Number(body.onhand); - await productStock.save(); + await AppDataSource.transaction(async (manager) => { + productStock.onhand = Number(body.onhand); + productStock.last_action = `Update Stock`; + productStock.actor = me.logName; + await manager.getRepository(ProductStock).save(productStock); + + variant.status = VariantStatus.Available; + await manager.getRepository(ProductVariant).save(variant); + + product.status = ProductStatus.Available; + await manager.getRepository(Product).save(product); + }); } await response.item(productStock, StockTransformer); diff --git a/src/database/entities/core/customer.entity.ts b/src/database/entities/core/customer.entity.ts index 5ce27c7..6116fd8 100644 --- a/src/database/entities/core/customer.entity.ts +++ b/src/database/entities/core/customer.entity.ts @@ -62,4 +62,8 @@ export class Customer extends BaseEntity { get isValid() { return this.isVerified && this.isActive; } + + get logName() { + return `${this.name} ${this.email || this.phone ? `<${this.email || this.phone}>` : ''}`; + } } diff --git a/src/database/entities/owner/product-history.entity.ts b/src/database/entities/owner/product-history.entity.ts index db8d5ff..e00294e 100644 --- a/src/database/entities/owner/product-history.entity.ts +++ b/src/database/entities/owner/product-history.entity.ts @@ -17,6 +17,9 @@ export class ProductHistory extends BaseEntity { @JsonColumn() data: any; + @Column() + actor: string; + @CreateDateColumn() created_at: Date; diff --git a/src/database/migrations/1726768379070-history-actor.ts b/src/database/migrations/1726768379070-history-actor.ts new file mode 100644 index 0000000..319d22e --- /dev/null +++ b/src/database/migrations/1726768379070-history-actor.ts @@ -0,0 +1,13 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class HistoryActor1726768379070 implements MigrationInterface { + name = 'HistoryActor1726768379070'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`product_history\` ADD \`actor\` varchar(255) NULL`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE \`product_history\` DROP COLUMN \`actor\``); + } +} diff --git a/src/database/subscribers/product-stock.subscriber.ts b/src/database/subscribers/product-stock.subscriber.ts index 4f14174..1e5d204 100644 --- a/src/database/subscribers/product-stock.subscriber.ts +++ b/src/database/subscribers/product-stock.subscriber.ts @@ -53,6 +53,7 @@ export class ProductStockSubscriber implements EntitySubscriberInterface