diff --git a/env/env.development b/env/env.development index 1e241d4..ac7e894 100644 --- a/env/env.development +++ b/env/env.development @@ -46,4 +46,8 @@ SUPERSET_ADMIN_USERNAME=admin SUPERSET_ADMIN_PASSWORD=admin WHATSAPP_BUSINESS_ACCOUNT_NUMBER_ID=604883366037548 -WHATSAPP_BUSINESS_ACCESS_TOKEN=EAAINOvRRiEEBO9yQsYDnYtjHZB7q1nZCwbBpRcxIGMDWajKZBtmWxNRKvPYkS95KQZBsZBOvSFyjiEg5CcCZBZBtaSZApxyV8fiA3cEyVwf7iVZBQP2YCTPRQZArMFeeXbO0uq5TGygmjsIz3M4YxcUHxPzKO4pKxIyxnzcoUZCqCSo1NqQSLVf3a0JyZAwgDXGL55dV \ No newline at end of file +WHATSAPP_BUSINESS_ACCESS_TOKEN=EAAINOvRRiEEBO9yQsYDnYtjHZB7q1nZCwbBpRcxIGMDWajKZBtmWxNRKvPYkS95KQZBsZBOvSFyjiEg5CcCZBZBtaSZApxyV8fiA3cEyVwf7iVZBQP2YCTPRQZArMFeeXbO0uq5TGygmjsIz3M4YxcUHxPzKO4pKxIyxnzcoUZCqCSo1NqQSLVf3a0JyZAwgDXGL55dV + +SETUP_SCHEDULING_KEY=scheduling_key_example + +SKIP_TRANSACTION_FEATURE=false \ No newline at end of file diff --git a/env/env.production b/env/env.production index fce7b16..05de872 100644 --- a/env/env.production +++ b/env/env.production @@ -43,4 +43,8 @@ SUPERSET_ADMIN_USERNAME=admin SUPERSET_ADMIN_PASSWORD=admin WHATSAPP_BUSINESS_ACCOUNT_NUMBER_ID=604883366037548 -WHATSAPP_BUSINESS_ACCESS_TOKEN=EAAINOvRRiEEBO9yQsYDnYtjHZB7q1nZCwbBpRcxIGMDWajKZBtmWxNRKvPYkS95KQZBsZBOvSFyjiEg5CcCZBZBtaSZApxyV8fiA3cEyVwf7iVZBQP2YCTPRQZArMFeeXbO0uq5TGygmjsIz3M4YxcUHxPzKO4pKxIyxnzcoUZCqCSo1NqQSLVf3a0JyZAwgDXGL55dV \ No newline at end of file +WHATSAPP_BUSINESS_ACCESS_TOKEN=EAAINOvRRiEEBO9yQsYDnYtjHZB7q1nZCwbBpRcxIGMDWajKZBtmWxNRKvPYkS95KQZBsZBOvSFyjiEg5CcCZBZBtaSZApxyV8fiA3cEyVwf7iVZBQP2YCTPRQZArMFeeXbO0uq5TGygmjsIz3M4YxcUHxPzKO4pKxIyxnzcoUZCqCSo1NqQSLVf3a0JyZAwgDXGL55dV + +SETUP_SCHEDULING_KEY=scheduling_key_example + +SKIP_TRANSACTION_FEATURE=false \ No newline at end of file diff --git a/package.json b/package.json index ef1f732..bb8761e 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "mathjs": "^13.0.2", "midtrans-client": "^1.3.1", "moment": "^2.30.1", + "moment-timezone": "^0.6.0", "nano": "^10.1.3", "nodemailer": "^6.9.14", "pdfmake": "^0.2.10", diff --git a/src/app.module.ts b/src/app.module.ts index cef2349..0af318e 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -107,6 +107,10 @@ import { OtpVerificationModel } from './modules/configuration/otp-verification/d import { OtpVerifierModel } from './modules/configuration/otp-verification/data/models/otp-verifier.model'; import { RescheduleVerificationModel } from './modules/booking-online/order/data/models/reschedule-verification.model'; import { OtpCheckerGuard } from './core/guards/domain/otp-checker.guard'; +import { DataSchedulingModel } from './modules/configuration/data-scheduling/data/models/data-scheduling.model'; +import { DataSchedulingModule } from './modules/configuration/data-scheduling/data-scheduling.module'; +import { DataSchedulingDefaultModel } from './modules/configuration/data-scheduling/data/models/data-scheduling-default.model'; +import { DataSchedulingLogModel } from './modules/configuration/data-scheduling/data/models/data-scheduling-log.model'; @Module({ imports: [ @@ -176,6 +180,11 @@ import { OtpCheckerGuard } from './core/guards/domain/otp-checker.guard'; OtpVerificationModel, OtpVerifierModel, + + // Data Scheduling + DataSchedulingModel, + DataSchedulingDefaultModel, + DataSchedulingLogModel, ], synchronize: false, }), @@ -242,6 +251,7 @@ import { OtpCheckerGuard } from './core/guards/domain/otp-checker.guard'; BookingOnlineAuthModule, BookingOrderModule, OtpVerificationModule, + DataSchedulingModule, ], controllers: [], providers: [ diff --git a/src/core/strings/constants/module.constants.ts b/src/core/strings/constants/module.constants.ts index 8e8896e..64fedc4 100644 --- a/src/core/strings/constants/module.constants.ts +++ b/src/core/strings/constants/module.constants.ts @@ -33,4 +33,9 @@ export enum MODULE_NAME { OTP_VERIFICATIONS = 'otp-verification', OTP_VERIFIER = 'otp-verifier', + DATA_SCHEDULING = 'data-scheduling', + DATA_SCHEDULING_LOG = 'data-scheduling-log', + DATA_SCHEDULING_DEFAULT = 'data-scheduling-default', + DATA_SCHEDULING_ACTIVE = 'data-scheduling-active', + DATA_SCHEDULING_SETUP = 'data-scheduling-setup', } diff --git a/src/core/strings/constants/table.constants.ts b/src/core/strings/constants/table.constants.ts index 2139ba6..e8990d3 100644 --- a/src/core/strings/constants/table.constants.ts +++ b/src/core/strings/constants/table.constants.ts @@ -12,7 +12,7 @@ export enum TABLE_NAME { NEWS = 'news', PAYMENT_METHOD = 'payment_methods', PRICE_FORMULA = 'price_formulas', - TRANSACTION_SETTING = 'transaction_settings', + REFUND = 'refunds', REFUND_ITEM = 'refund_items', SEASON_TYPE = 'season_types', @@ -27,6 +27,8 @@ export enum TABLE_NAME { TRANSACTION_ITEM_TAX = 'transaction_item_taxes', TRANSACTION_ITEM_BREAKDOWN_TAX = 't_breakdown_item_taxes', TRANSACTION_DEMOGRAPHY = 'transaction_demographies', + TRANSACTION_SETTING = 'api_settings', + USER = 'users', USER_LOGIN = 'users_login', LOG_USER_LOGIN = 'log_users_login', @@ -47,4 +49,8 @@ export enum TABLE_NAME { TIME_GROUPS = 'time_groups', OTP_VERIFICATIONS = 'otp_verifications', OTP_VERIFIER = 'otp_verifier', + + DATA_SCHEDULING = 'event_scheduling', + DATA_SCHEDULING_DEFAULT = 'event_scheduling_default', + DATA_SCHEDULING_LOG = 'event_scheduling_log', } diff --git a/src/database/migrations/1745991391299-transaction-setting-model.ts b/src/database/migrations/1745991391299-transaction-setting-model.ts deleted file mode 100644 index f59344a..0000000 --- a/src/database/migrations/1745991391299-transaction-setting-model.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; - -export class TransactionSettingModel1745991391299 - implements MigrationInterface -{ - name = 'TransactionSettingModel1745991391299'; - - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `CREATE TABLE "transaction_settings" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "creator_id" character varying(36), "creator_name" character varying(125), "editor_id" character varying(36), "editor_name" character varying(125), "created_at" bigint NOT NULL, "updated_at" bigint NOT NULL, "value" numeric NOT NULL DEFAULT '100', CONSTRAINT "PK_db7fb38a439358b499ebdee4761" PRIMARY KEY ("id"))`, - ); - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`DROP TABLE "transaction_settings"`); - } -} diff --git a/src/database/migrations/1751942902581-renam-table-config.ts b/src/database/migrations/1751942902581-renam-table-config.ts new file mode 100644 index 0000000..7cd4dfa --- /dev/null +++ b/src/database/migrations/1751942902581-renam-table-config.ts @@ -0,0 +1,38 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class RenamTableConfig1751942902581 implements MigrationInterface { + name = 'RenamTableConfig1751942902581'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "api_settings" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "creator_id" character varying(36), "creator_name" character varying(125), "editor_id" character varying(36), "editor_name" character varying(125), "created_at" bigint NOT NULL, "updated_at" bigint NOT NULL, "value" numeric NOT NULL DEFAULT '100', CONSTRAINT "PK_14bbb118ae1b2bd2385186cffb9" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `CREATE TYPE "public"."event_scheduling_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `CREATE TABLE "event_scheduling" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "creator_id" character varying(36), "creator_name" character varying(125), "editor_id" character varying(36), "editor_name" character varying(125), "created_at" bigint NOT NULL, "updated_at" bigint NOT NULL, "status" "public"."event_scheduling_status_enum" NOT NULL DEFAULT 'draft', "name" character varying NOT NULL, "indexing_key" character varying NOT NULL, "schedule_date_from" date NOT NULL, "schedule_date_to" date NOT NULL, CONSTRAINT "PK_a2ccd3f6ab787b0d7e2af09d30c" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `CREATE TABLE "event_scheduling_default" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "creator_id" character varying(36), "creator_name" character varying(125), "editor_id" character varying(36), "editor_name" character varying(125), "created_at" bigint NOT NULL, "updated_at" bigint NOT NULL, "default_value" integer NOT NULL, CONSTRAINT "PK_9caf65330e76243e9f9285ae2e1" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `ALTER TABLE "api_settings" ADD "key" character varying`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "event_scheduling_default"`); + await queryRunner.query(`DROP TABLE "event_scheduling"`); + await queryRunner.query( + `DROP TYPE "public"."event_scheduling_status_enum"`, + ); + await queryRunner.query(`DROP TABLE "api_settings"`); + await queryRunner.query( + `ALTER TABLE "item_bundlings" ADD CONSTRAINT "FK_a50e7abf2caba4d0394f3726b86" FOREIGN KEY ("item_bundling_id") REFERENCES "items"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "queue_tickets" ADD CONSTRAINT "FK_0e9823b8b7ca9523b3be73878e5" FOREIGN KEY ("order_id") REFERENCES "queue_orders"("id") ON DELETE SET NULL ON UPDATE CASCADE`, + ); + } +} diff --git a/src/database/migrations/1752146975330-data-scheduling-log.ts b/src/database/migrations/1752146975330-data-scheduling-log.ts new file mode 100644 index 0000000..619f1f0 --- /dev/null +++ b/src/database/migrations/1752146975330-data-scheduling-log.ts @@ -0,0 +1,33 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class DataSchedulingLog1752146975330 implements MigrationInterface { + name = 'DataSchedulingLog1752146975330'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."event_scheduling_log_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `CREATE TYPE "public"."event_scheduling_log_type_enum" AS ENUM('Default Percentage', 'Data Scheduling')`, + ); + await queryRunner.query( + `CREATE TYPE "public"."event_scheduling_log_action_enum" AS ENUM('CREATE', 'UPDATE', 'DELETE', 'CHANGE_STATUS')`, + ); + await queryRunner.query( + `CREATE TABLE "event_scheduling_log" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "creator_id" character varying(36), "creator_name" character varying(125), "editor_id" character varying(36), "editor_name" character varying(125), "created_at" bigint NOT NULL, "updated_at" bigint NOT NULL, "status" "public"."event_scheduling_log_status_enum" NOT NULL DEFAULT 'draft', "type" "public"."event_scheduling_log_type_enum" NOT NULL, "action" "public"."event_scheduling_log_action_enum" NOT NULL, "log_created_at" bigint NOT NULL, "data_id" character varying, "name" character varying, "indexing_key" character varying, "schedule_date_from" date, "schedule_date_to" date, "default_value" integer, "description" text, CONSTRAINT "PK_984247db566636baacab18f593a" PRIMARY KEY ("id"))`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "event_scheduling_log"`); + await queryRunner.query( + `DROP TYPE "public"."event_scheduling_log_action_enum"`, + ); + await queryRunner.query( + `DROP TYPE "public"."event_scheduling_log_type_enum"`, + ); + await queryRunner.query( + `DROP TYPE "public"."event_scheduling_log_status_enum"`, + ); + } +} diff --git a/src/modules/configuration/couch/constants.ts b/src/modules/configuration/couch/constants.ts index fa8be79..3b8c14a 100644 --- a/src/modules/configuration/couch/constants.ts +++ b/src/modules/configuration/couch/constants.ts @@ -4,4 +4,5 @@ export const DatabaseListen = [ 'pos_activity', 'pos_cash_activity', 'time_groups', + 'api_configuration', ]; diff --git a/src/modules/configuration/couch/couch.module.ts b/src/modules/configuration/couch/couch.module.ts index 060254c..2698071 100644 --- a/src/modules/configuration/couch/couch.module.ts +++ b/src/modules/configuration/couch/couch.module.ts @@ -57,6 +57,10 @@ import { TimeGroupUpdatedHandler, } from './domain/managers/time-group.handle'; +import { DataSchedulingUpdatedHandler } from './domain/managers/data-scheduling.handler'; +import { DataSchedulingDefaultModel } from '../data-scheduling/data/models/data-scheduling-default.model'; +import { DataSchedulingModel } from '../data-scheduling/data/models/data-scheduling.model'; + @Module({ imports: [ ConfigModule.forRoot(), @@ -71,6 +75,9 @@ import { TransactionTaxModel, TransactionItemModel, TransactionDemographyModel, + + DataSchedulingDefaultModel, + DataSchedulingModel, ], CONNECTION_NAME.DEFAULT, ), @@ -104,6 +111,8 @@ import { SeasonTypeDeletedHandler, SeasonTypeUpdatedHandler, + DataSchedulingUpdatedHandler, + SeasonPeriodDataService, TransactionDataService, UserDataService, diff --git a/src/modules/configuration/couch/data/services/couch.service.ts b/src/modules/configuration/couch/data/services/couch.service.ts index 2efb2db..97c5991 100644 --- a/src/modules/configuration/couch/data/services/couch.service.ts +++ b/src/modules/configuration/couch/data/services/couch.service.ts @@ -23,21 +23,23 @@ export class CouchService { const nano = this.nanoInstance; for (const database of DatabaseListen) { const db = nano.db.use(database); - db.changesReader.start({ includeDocs: true }).on('change', (change) => { - Logger.verbose( - `Receive Data from ${database}: ${change?.id}`, - 'CouchService', - ); - this.changeDoc(change, database); - }); + db.changesReader + .start({ includeDocs: true }) + .on('change', async (change) => { + Logger.verbose( + `Receive Data from ${database}: ${change?.id}`, + 'CouchService', + ); + await this.changeDoc(change, database); + }); // transaction Logger.log(`start listen database ${database}`, 'CouchService'); } } - private changeDoc(data, database) { - this.eventBus.publish( + private async changeDoc(data, database) { + await this.eventBus.publish( new ChangeDocEvent({ id: data.id, database: database, @@ -112,6 +114,7 @@ export class CouchService { created_at: { $gte: todayTimestamp, }, + status: 'settled', }; const result = await db.find({ diff --git a/src/modules/configuration/couch/domain/managers/data-scheduling.handler.ts b/src/modules/configuration/couch/domain/managers/data-scheduling.handler.ts new file mode 100644 index 0000000..c61e3b2 --- /dev/null +++ b/src/modules/configuration/couch/domain/managers/data-scheduling.handler.ts @@ -0,0 +1,90 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { CouchService } from '../../data/services/couch.service'; +import { DataSchedulingDeletedEvent } from 'src/modules/configuration/data-scheduling/domain/entities/event/data-scheduling-deleted.event'; +import { DataSchedulingChangeStatusEvent } from 'src/modules/configuration/data-scheduling/domain/entities/event/data-scheduling-change-status.event'; +import { DataSchedulingUpdatedEvent } from 'src/modules/configuration/data-scheduling/domain/entities/event/data-scheduling-updated.event'; +import { DataSchedulingCreatedEvent } from 'src/modules/configuration/data-scheduling/domain/entities/event/data-scheduling-created.event'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DataSchedulingDefaultModel } from 'src/modules/configuration/data-scheduling/data/models/data-scheduling-default.model'; +import { DataSchedulingModel } from 'src/modules/configuration/data-scheduling/data/models/data-scheduling.model'; +import { Repository } from 'typeorm'; + +import * as momentTz from 'moment-timezone'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { DataSchedulingEntity } from 'src/modules/configuration/data-scheduling/domain/entities/data-scheduling.entity'; +import { decryptionTotal } from 'src/modules/configuration/data-scheduling/infrastructure/helpers'; +import { Logger } from '@nestjs/common'; + +@EventsHandler( + DataSchedulingCreatedEvent, + DataSchedulingUpdatedEvent, + DataSchedulingChangeStatusEvent, + DataSchedulingDeletedEvent, +) +export class DataSchedulingUpdatedHandler implements IEventHandler { + private readonly logger = new Logger(DataSchedulingUpdatedHandler.name); + private readonly permanentID = 'e6166c86-d85d-43f8-86ad-c9e85a88f68f'; + private readonly couchTableName = 'api_configuration'; + + constructor( + private couchService: CouchService, + + @InjectRepository(DataSchedulingDefaultModel) + private repository: Repository, + + @InjectRepository(DataSchedulingModel) + private repoSchedule: Repository, + ) {} + + async handle() { + const activeData = await this.getActiveData(); + const existData = await this.couchService.getDoc( + this.permanentID, + this.couchTableName, + ); + if (!existData) { + this.logger.verbose('CREATE SCHEDULING CONFIG'); + await this.couchService.createDoc( + { + _id: this.permanentID, + id: this.permanentID, + ...activeData, + }, + this.couchTableName, + ); + } else if (existData) { + this.logger.verbose('UPDATE SCHEDULING CONFIG'); + await this.couchService.updateDoc( + { + _id: this.permanentID, + id: this.permanentID, + ...activeData, + }, + this.couchTableName, + ); + } + } + + async getActiveData() { + const timeZoneWIB = 'Asia/Jakarta'; + const nowInWIB = momentTz().tz(timeZoneWIB).format('YYYY-MM-DD'); + const date = nowInWIB; + + const qb = this.repoSchedule.createQueryBuilder(TABLE_NAME.DATA_SCHEDULING); + + const findData: DataSchedulingEntity = await qb + .where('status = :status', { status: 'active' }) + .andWhere('schedule_date_from <= :date', { date: date }) + .andWhere('schedule_date_to >= :date', { date: date }) + .getOne(); + + if (!findData) { + const defaultData = await this.repository + .createQueryBuilder(TABLE_NAME.DATA_SCHEDULING_DEFAULT) + .getOne(); + return { value: defaultData?.default_value }; + } + + return { value: decryptionTotal(findData.indexing_key as string), date }; + } +} diff --git a/src/modules/configuration/data-scheduling/data-scheduling.module.ts b/src/modules/configuration/data-scheduling/data-scheduling.module.ts new file mode 100644 index 0000000..3f786ed --- /dev/null +++ b/src/modules/configuration/data-scheduling/data-scheduling.module.ts @@ -0,0 +1,105 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { DataSchedulingDataService } from './data/services/data-scheduling-data.service'; +import { DataSchedulingReadService } from './data/services/data-scheduling-read.service'; +import { + DataSchedulingLogReadController, + DataSchedulingReadController, +} from './infrastructure/data-scheduling-read.controller'; +import { DataSchedulingReadOrchestrator } from './domain/usecases/data-scheduling-read.orchestrator'; +import { + DataSchedulingDataController, + DataSchedulingDefaultController, + DataSchedulingDataLogController, + DataSchedulingSetupController, +} from './infrastructure/data-scheduling-data.controller'; +import { DataSchedulingDataOrchestrator } from './domain/usecases/data-scheduling-data.orchestrator'; +import { CreateDataSchedulingManager } from './domain/usecases/managers/create-data-scheduling.manager'; +import { CqrsModule } from '@nestjs/cqrs'; +import { IndexDataSchedulingManager } from './domain/usecases/managers/index-data-scheduling.manager'; +import { DeleteDataSchedulingManager } from './domain/usecases/managers/delete-data-scheduling.manager'; +import { UpdateDataSchedulingManager } from './domain/usecases/managers/update-data-scheduling.manager'; +import { ActiveDataSchedulingManager } from './domain/usecases/managers/active-data-scheduling.manager'; +import { ConfirmDataSchedulingManager } from './domain/usecases/managers/confirm-data-scheduling.manager'; +import { InactiveDataSchedulingManager } from './domain/usecases/managers/inactive-data-scheduling.manager'; +import { DetailDataSchedulingManager } from './domain/usecases/managers/detail-data-scheduling.manager'; +import { BatchDeleteDataSchedulingManager } from './domain/usecases/managers/batch-delete-data-scheduling.manager'; +import { BatchActiveDataSchedulingManager } from './domain/usecases/managers/batch-active-data-scheduling.manager'; +import { BatchConfirmDataSchedulingManager } from './domain/usecases/managers/batch-confirm-data-scheduling.manager'; +import { BatchInactiveDataSchedulingManager } from './domain/usecases/managers/batch-inactive-data-scheduling.manager'; +import { DataSchedulingModel } from './data/models/data-scheduling.model'; +import { DataSchedulingDefaultModel } from './data/models/data-scheduling-default.model'; +import { DataSchedulingManager } from './domain/usecases/managers/data-scheduling-default.manager'; +import { SetupSchedulingGuard } from './infrastructure/guards/setup-scheduling.guard'; + +import { DataSchedulingChangeStatusHandler } from './domain/usecases/handlers/data-scheduling-change-status.handler'; +import { DataSchedulingCreatedHandler } from './domain/usecases/handlers/data-scheduling-created.handler'; +import { DataSchedulingDeletedHandler } from './domain/usecases/handlers/data-scheduling-deleted.handler'; +import { DataSchedulingUpdatedHandler } from './domain/usecases/handlers/data-scheduling-updated.handler'; + +import { JwtModule } from '@nestjs/jwt'; +import { JWT_EXPIRED } from 'src/core/sessions/constants'; +import { JWT_SECRET } from 'src/core/sessions/constants'; + +import { DataSchedulingLogDataService } from './data/services/data-scheduling-log-data.service'; +import { DataSchedulingLogModel } from './data/models/data-scheduling-log.model'; +import { DataSchedulingLogReadService } from './data/services/data-scheduling-log-read.service'; +import { IndexDataSchedulingLogManager } from './domain/usecases/managers/index-data-scheduling-log.manager'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature( + [DataSchedulingModel, DataSchedulingDefaultModel, DataSchedulingLogModel], + CONNECTION_NAME.DEFAULT, + ), + JwtModule.register({ + secret: JWT_SECRET, + signOptions: { expiresIn: JWT_EXPIRED }, + }), + CqrsModule, + ], + controllers: [ + DataSchedulingDataController, + DataSchedulingReadController, + DataSchedulingDefaultController, + DataSchedulingSetupController, + DataSchedulingLogReadController, + DataSchedulingDataLogController, + ], + providers: [ + SetupSchedulingGuard, + IndexDataSchedulingManager, + DetailDataSchedulingManager, + CreateDataSchedulingManager, + DeleteDataSchedulingManager, + UpdateDataSchedulingManager, + ActiveDataSchedulingManager, + ConfirmDataSchedulingManager, + InactiveDataSchedulingManager, + BatchDeleteDataSchedulingManager, + BatchActiveDataSchedulingManager, + BatchConfirmDataSchedulingManager, + BatchInactiveDataSchedulingManager, + + DataSchedulingLogDataService, + DataSchedulingLogReadService, + DataSchedulingDataService, + DataSchedulingReadService, + DataSchedulingLogDataService, + + DataSchedulingDataOrchestrator, + DataSchedulingReadOrchestrator, + + DataSchedulingManager, + IndexDataSchedulingLogManager, + + DataSchedulingChangeStatusHandler, + DataSchedulingCreatedHandler, + DataSchedulingDeletedHandler, + DataSchedulingUpdatedHandler, + ], +}) +export class DataSchedulingModule {} diff --git a/src/modules/configuration/data-scheduling/data/models/data-scheduling-default.model.ts b/src/modules/configuration/data-scheduling/data/models/data-scheduling-default.model.ts new file mode 100644 index 0000000..2ff8728 --- /dev/null +++ b/src/modules/configuration/data-scheduling/data/models/data-scheduling-default.model.ts @@ -0,0 +1,13 @@ +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { DataSchedulingDefaultEntity } from '../../domain/entities/data-scheduling.entity'; +import { Column, Entity } from 'typeorm'; +import { BaseModel } from 'src/core/modules/data/model/base.model'; + +@Entity(TABLE_NAME.DATA_SCHEDULING_DEFAULT) +export class DataSchedulingDefaultModel + extends BaseModel + implements DataSchedulingDefaultEntity +{ + @Column('int', { nullable: false }) + default_value: number; +} diff --git a/src/modules/configuration/data-scheduling/data/models/data-scheduling-log.model.ts b/src/modules/configuration/data-scheduling/data/models/data-scheduling-log.model.ts new file mode 100644 index 0000000..4950656 --- /dev/null +++ b/src/modules/configuration/data-scheduling/data/models/data-scheduling-log.model.ts @@ -0,0 +1,44 @@ +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { + DataSchedulingLogEntity, + SCHEDULING_LOG_ACTION_ENUM, + SCHEDULING_LOG_TYPE_ENUM, +} from '../../domain/entities/data-scheduling.entity'; +import { Column, Entity } from 'typeorm'; +import { BaseStatusModel } from 'src/core/modules/data/model/base-status.model'; + +@Entity(TABLE_NAME.DATA_SCHEDULING_LOG) +export class DataSchedulingLogModel + extends BaseStatusModel + implements DataSchedulingLogEntity +{ + @Column({ type: 'enum', enum: SCHEDULING_LOG_TYPE_ENUM, nullable: false }) + type: SCHEDULING_LOG_TYPE_ENUM; + + @Column({ type: 'enum', enum: SCHEDULING_LOG_ACTION_ENUM, nullable: false }) + action: SCHEDULING_LOG_ACTION_ENUM; + + @Column({ type: 'bigint', nullable: false }) + log_created_at: number; + + @Column('varchar', { name: 'data_id', nullable: true }) + data_id: string; + + @Column('varchar', { name: 'name', nullable: true }) + name: string; + + @Column('varchar', { name: 'indexing_key', nullable: true }) + indexing_key: string; + + @Column('date', { name: 'schedule_date_from', nullable: true }) + schedule_date_from: Date; + + @Column('date', { name: 'schedule_date_to', nullable: true }) + schedule_date_to: Date; + + @Column('int', { nullable: true }) + default_value: number; + + @Column('text', { name: 'description', nullable: true }) + description: string; +} diff --git a/src/modules/configuration/data-scheduling/data/models/data-scheduling.model.ts b/src/modules/configuration/data-scheduling/data/models/data-scheduling.model.ts new file mode 100644 index 0000000..a806445 --- /dev/null +++ b/src/modules/configuration/data-scheduling/data/models/data-scheduling.model.ts @@ -0,0 +1,22 @@ +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { DataSchedulingEntity } from '../../domain/entities/data-scheduling.entity'; +import { Column, Entity } from 'typeorm'; +import { BaseStatusModel } from 'src/core/modules/data/model/base-status.model'; + +@Entity(TABLE_NAME.DATA_SCHEDULING) +export class DataSchedulingModel + extends BaseStatusModel + implements DataSchedulingEntity +{ + @Column('varchar', { name: 'name' }) + name: string; + + @Column('varchar', { name: 'indexing_key' }) + indexing_key: string; + + @Column('date', { name: 'schedule_date_from', nullable: false }) + schedule_date_from: Date; + + @Column('date', { name: 'schedule_date_to', nullable: false }) + schedule_date_to: Date; +} diff --git a/src/modules/configuration/data-scheduling/data/services/data-scheduling-data.service.ts b/src/modules/configuration/data-scheduling/data/services/data-scheduling-data.service.ts new file mode 100644 index 0000000..689dd20 --- /dev/null +++ b/src/modules/configuration/data-scheduling/data/services/data-scheduling-data.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; +import { DataSchedulingEntity } from '../../domain/entities/data-scheduling.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DataSchedulingModel } from '../models/data-scheduling.model'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { Repository } from 'typeorm'; + +@Injectable() +export class DataSchedulingDataService extends BaseDataService { + constructor( + @InjectRepository(DataSchedulingModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/configuration/data-scheduling/data/services/data-scheduling-log-data.service.ts b/src/modules/configuration/data-scheduling/data/services/data-scheduling-log-data.service.ts new file mode 100644 index 0000000..d86b824 --- /dev/null +++ b/src/modules/configuration/data-scheduling/data/services/data-scheduling-log-data.service.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { DataSchedulingLogModel } from '../models/data-scheduling-log.model'; +import { Repository } from 'typeorm'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@Injectable() +export class DataSchedulingLogDataService { + constructor( + @InjectRepository(DataSchedulingLogModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) {} + + async create(entity: any): Promise { + return await this.repo.save(entity); + } + + async deleteRange(from: number, to: number): Promise { + try { + const deleteResult = await this.repo + .createQueryBuilder() + .delete() + .from(TABLE_NAME.DATA_SCHEDULING_LOG) + .where('log_created_at BETWEEN :from AND :to', { from, to }) + .execute(); + + return deleteResult; + } catch (error) { + throw new Error('Failed to delete range due to an internal error.'); + } + } +} diff --git a/src/modules/configuration/data-scheduling/data/services/data-scheduling-log-read.service.ts b/src/modules/configuration/data-scheduling/data/services/data-scheduling-log-read.service.ts new file mode 100644 index 0000000..21943ab --- /dev/null +++ b/src/modules/configuration/data-scheduling/data/services/data-scheduling-log-read.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { DataSchedulingLogEntity } from '../../domain/entities/data-scheduling.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { BaseReadService } from 'src/core/modules/data/service/base-read.service'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { Repository } from 'typeorm'; +import { DataSchedulingLogModel } from '../models/data-scheduling-log.model'; + +@Injectable() +export class DataSchedulingLogReadService extends BaseReadService { + constructor( + @InjectRepository(DataSchedulingLogModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/configuration/data-scheduling/data/services/data-scheduling-read.service.ts b/src/modules/configuration/data-scheduling/data/services/data-scheduling-read.service.ts new file mode 100644 index 0000000..7385da0 --- /dev/null +++ b/src/modules/configuration/data-scheduling/data/services/data-scheduling-read.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { DataSchedulingEntity } from '../../domain/entities/data-scheduling.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DataSchedulingModel } from '../models/data-scheduling.model'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { Repository } from 'typeorm'; +import { BaseReadService } from 'src/core/modules/data/service/base-read.service'; + +@Injectable() +export class DataSchedulingReadService extends BaseReadService { + constructor( + @InjectRepository(DataSchedulingModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/configuration/data-scheduling/domain/entities/data-scheduling.entity.ts b/src/modules/configuration/data-scheduling/domain/entities/data-scheduling.entity.ts new file mode 100644 index 0000000..ea2e92b --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/entities/data-scheduling.entity.ts @@ -0,0 +1,47 @@ +import { BaseStatusEntity } from 'src/core/modules/domain/entities/base-status.entity'; +import { BaseEntity } from 'src/core/modules/domain/entities/base.entity'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +export interface DataSchedulingEntity extends BaseStatusEntity { + name: string; + indexing_key: number | string; + schedule_date_from: Date; + schedule_date_to: Date; +} + +export interface DataSchedulingDefaultEntity extends BaseEntity { + default_value: number; +} + +export interface DataSchedulingActiveEntity { + value: number; +} + +export enum SCHEDULING_LOG_TYPE_ENUM { + DEFAULT_PERCENTAGE = 'Default Percentage', + DATA_SCHEDULING = 'Data Scheduling', +} + +export enum SCHEDULING_LOG_ACTION_ENUM { + CREATE = 'CREATE', + UPDATE = 'UPDATE', + DELETE = 'DELETE', + CHANGE_STATUS = 'CHANGE_STATUS', +} + +export interface DataSchedulingLogEntity extends BaseStatusEntity { + type: SCHEDULING_LOG_TYPE_ENUM; + action: SCHEDULING_LOG_ACTION_ENUM; + log_created_at: number; + + data_id?: string; + + name?: string; + indexing_key?: number | string; + schedule_date_from?: Date; + schedule_date_to?: Date; + + default_value?: number; + + description?: string; +} diff --git a/src/modules/configuration/data-scheduling/domain/entities/event/data-scheduling-change-status.event.ts b/src/modules/configuration/data-scheduling/domain/entities/event/data-scheduling-change-status.event.ts new file mode 100644 index 0000000..86c8e9c --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/entities/event/data-scheduling-change-status.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class DataSchedulingChangeStatusEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/configuration/data-scheduling/domain/entities/event/data-scheduling-created.event.ts b/src/modules/configuration/data-scheduling/domain/entities/event/data-scheduling-created.event.ts new file mode 100644 index 0000000..0ce8274 --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/entities/event/data-scheduling-created.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class DataSchedulingCreatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/configuration/data-scheduling/domain/entities/event/data-scheduling-deleted.event.ts b/src/modules/configuration/data-scheduling/domain/entities/event/data-scheduling-deleted.event.ts new file mode 100644 index 0000000..592233a --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/entities/event/data-scheduling-deleted.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class DataSchedulingDeletedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/configuration/data-scheduling/domain/entities/event/data-scheduling-updated.event.ts b/src/modules/configuration/data-scheduling/domain/entities/event/data-scheduling-updated.event.ts new file mode 100644 index 0000000..e888703 --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/entities/event/data-scheduling-updated.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class DataSchedulingUpdatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/configuration/data-scheduling/domain/entities/filter-data-scheduling.entity.ts b/src/modules/configuration/data-scheduling/domain/entities/filter-data-scheduling.entity.ts new file mode 100644 index 0000000..5ea5426 --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/entities/filter-data-scheduling.entity.ts @@ -0,0 +1,6 @@ +import { BaseFilterEntity } from 'src/core/modules/domain/entities/base-filter.entity'; + +export interface FilterDataSchedulingEntity extends BaseFilterEntity { + schedule_date_from: Date; + schedule_date_to: Date; +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/data-scheduling-data.orchestrator.ts b/src/modules/configuration/data-scheduling/domain/usecases/data-scheduling-data.orchestrator.ts new file mode 100644 index 0000000..086bcef --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/data-scheduling-data.orchestrator.ts @@ -0,0 +1,125 @@ +import { Injectable } from '@nestjs/common'; +import { CreateDataSchedulingManager } from './managers/create-data-scheduling.manager'; +import { DataSchedulingDataService } from '../../data/services/data-scheduling-data.service'; +import { DataSchedulingEntity } from '../entities/data-scheduling.entity'; +import { DeleteDataSchedulingManager } from './managers/delete-data-scheduling.manager'; +import { UpdateDataSchedulingManager } from './managers/update-data-scheduling.manager'; +import { BaseDataTransactionOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-data-transaction.orchestrator'; +import { ActiveDataSchedulingManager } from './managers/active-data-scheduling.manager'; +import { InactiveDataSchedulingManager } from './managers/inactive-data-scheduling.manager'; +import { ConfirmDataSchedulingManager } from './managers/confirm-data-scheduling.manager'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchConfirmDataSchedulingManager } from './managers/batch-confirm-data-scheduling.manager'; +import { BatchInactiveDataSchedulingManager } from './managers/batch-inactive-data-scheduling.manager'; +import { BatchActiveDataSchedulingManager } from './managers/batch-active-data-scheduling.manager'; +import { BatchDeleteDataSchedulingManager } from './managers/batch-delete-data-scheduling.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@Injectable() +export class DataSchedulingDataOrchestrator extends BaseDataTransactionOrchestrator { + constructor( + private createManager: CreateDataSchedulingManager, + private updateManager: UpdateDataSchedulingManager, + private deleteManager: DeleteDataSchedulingManager, + private activeManager: ActiveDataSchedulingManager, + private confirmManager: ConfirmDataSchedulingManager, + private inactiveManager: InactiveDataSchedulingManager, + private batchDeleteManager: BatchDeleteDataSchedulingManager, + private batchActiveManager: BatchActiveDataSchedulingManager, + private batchConfirmManager: BatchConfirmDataSchedulingManager, + private batchInactiveManager: BatchInactiveDataSchedulingManager, + private serviceData: DataSchedulingDataService, + ) { + super(); + } + + async create(data): Promise { + this.createManager.setData(data); + this.createManager.setService(this.serviceData, TABLE_NAME.DATA_SCHEDULING); + await this.createManager.execute(); + await this.createManager.generateConfig(); + return this.createManager.getResult(); + } + + async update(dataId, data): Promise { + this.updateManager.setData(dataId, data); + this.updateManager.setService(this.serviceData, TABLE_NAME.DATA_SCHEDULING); + await this.updateManager.execute(); + return this.updateManager.getResult(); + } + + async delete(dataId): Promise { + this.deleteManager.setData(dataId); + this.deleteManager.setService(this.serviceData, TABLE_NAME.DATA_SCHEDULING); + await this.deleteManager.execute(); + return this.deleteManager.getResult(); + } + + async batchDelete(dataIds: string[]): Promise { + this.batchDeleteManager.setData(dataIds); + this.batchDeleteManager.setService( + this.serviceData, + TABLE_NAME.DATA_SCHEDULING, + ); + await this.batchDeleteManager.execute(); + return this.batchDeleteManager.getResult(); + } + + async active(dataId): Promise { + this.activeManager.setData(dataId, STATUS.ACTIVE); + this.activeManager.setService(this.serviceData, TABLE_NAME.DATA_SCHEDULING); + await this.activeManager.execute(); + return this.activeManager.getResult(); + } + + async batchActive(dataIds: string[]): Promise { + this.batchActiveManager.setData(dataIds, STATUS.ACTIVE); + this.batchActiveManager.setService( + this.serviceData, + TABLE_NAME.DATA_SCHEDULING, + ); + await this.batchActiveManager.execute(); + return this.batchActiveManager.getResult(); + } + + async confirm(dataId): Promise { + this.confirmManager.setData(dataId, STATUS.ACTIVE); + this.confirmManager.setService( + this.serviceData, + TABLE_NAME.DATA_SCHEDULING, + ); + await this.confirmManager.execute(); + return this.confirmManager.getResult(); + } + + async batchConfirm(dataIds: string[]): Promise { + this.batchConfirmManager.setData(dataIds, STATUS.ACTIVE); + this.batchConfirmManager.setService( + this.serviceData, + TABLE_NAME.DATA_SCHEDULING, + ); + await this.batchConfirmManager.execute(); + return this.batchConfirmManager.getResult(); + } + + async inactive(dataId): Promise { + this.inactiveManager.setData(dataId, STATUS.INACTIVE); + this.inactiveManager.setService( + this.serviceData, + TABLE_NAME.DATA_SCHEDULING, + ); + await this.inactiveManager.execute(); + return this.inactiveManager.getResult(); + } + + async batchInactive(dataIds: string[]): Promise { + this.batchInactiveManager.setData(dataIds, STATUS.INACTIVE); + this.batchInactiveManager.setService( + this.serviceData, + TABLE_NAME.DATA_SCHEDULING, + ); + await this.batchInactiveManager.execute(); + return this.batchInactiveManager.getResult(); + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/data-scheduling-read.orchestrator.ts b/src/modules/configuration/data-scheduling/domain/usecases/data-scheduling-read.orchestrator.ts new file mode 100644 index 0000000..1f4da92 --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/data-scheduling-read.orchestrator.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@nestjs/common'; +import { IndexDataSchedulingManager } from './managers/index-data-scheduling.manager'; +import { DataSchedulingReadService } from '../../data/services/data-scheduling-read.service'; +import { + DataSchedulingEntity, + DataSchedulingLogEntity, +} from '../entities/data-scheduling.entity'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { BaseReadOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-read.orchestrator'; +import { DetailDataSchedulingManager } from './managers/detail-data-scheduling.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { IndexDataSchedulingLogManager } from './managers/index-data-scheduling-log.manager'; +import { DataSchedulingLogReadService } from '../../data/services/data-scheduling-log-read.service'; + +@Injectable() +export class DataSchedulingReadOrchestrator extends BaseReadOrchestrator { + constructor( + private indexManager: IndexDataSchedulingManager, + private indexLogManager: IndexDataSchedulingLogManager, + private detailManager: DetailDataSchedulingManager, + private serviceData: DataSchedulingReadService, + private logServiceData: DataSchedulingLogReadService, + ) { + super(); + } + + async index(params): Promise> { + this.indexManager.setFilterParam(params); + this.indexManager.setService(this.serviceData, TABLE_NAME.DATA_SCHEDULING); + await this.indexManager.execute(); + return this.indexManager.getResult(); + } + + async indexLog(params): Promise> { + this.indexLogManager.setFilterParam(params); + this.indexLogManager.setService( + this.logServiceData, + TABLE_NAME.DATA_SCHEDULING_LOG, + ); + await this.indexLogManager.execute(); + return this.indexLogManager.getResult(); + } + + async detail(dataId: string): Promise { + this.detailManager.setData(dataId); + this.detailManager.setService(this.serviceData, TABLE_NAME.DATA_SCHEDULING); + await this.detailManager.execute(); + return this.detailManager.getResult(); + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/handlers/data-scheduling-change-status.handler.ts b/src/modules/configuration/data-scheduling/domain/usecases/handlers/data-scheduling-change-status.handler.ts new file mode 100644 index 0000000..174ef1b --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/handlers/data-scheduling-change-status.handler.ts @@ -0,0 +1,63 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { DataSchedulingChangeStatusEvent } from '../../entities/event/data-scheduling-change-status.event'; +import { capitalizeEachWord } from 'src/modules/reports/shared/helpers'; +import { + DataSchedulingLogEntity, + SCHEDULING_LOG_ACTION_ENUM, + SCHEDULING_LOG_TYPE_ENUM, +} from '../../entities/data-scheduling.entity'; +import { Logger } from '@nestjs/common'; +import { DataSchedulingLogDataService } from '../../../data/services/data-scheduling-log-data.service'; +import { decryptionTotal } from '../../../infrastructure/helpers'; + +@EventsHandler(DataSchedulingChangeStatusEvent) +export class DataSchedulingChangeStatusHandler + implements IEventHandler +{ + private readonly logger = new Logger(DataSchedulingChangeStatusHandler.name); + + constructor(private service: DataSchedulingLogDataService) {} + + async handle(event: DataSchedulingChangeStatusEvent) { + // Prevent execution if the event data is null, which can happen if triggered from the default percentage update service. + if (event.data?.data) { + const oldData = event?.data?.old; + const newData = event?.data?.data; + + const oldStatus = capitalizeEachWord(oldData?.status); + const newStatus = capitalizeEachWord(newData?.status); + + const scheduleName = newData?.name || 'an item'; + const editorName = newData.editor_name || 'System'; + const totalPercentage = decryptionTotal(newData?.indexing_key); + + const description = `
${editorName} changed the status of ${scheduleName} (${totalPercentage}%) schedule from ${newData?.schedule_date_from} to ${newData.schedule_date_to} from ${oldStatus} to ${newStatus}.
`; + + const payload: DataSchedulingLogEntity = { + type: SCHEDULING_LOG_TYPE_ENUM.DATA_SCHEDULING, + action: SCHEDULING_LOG_ACTION_ENUM.CHANGE_STATUS, + log_created_at: new Date().getTime(), + + data_id: newData?.id, + name: newData?.name, + indexing_key: newData?.indexing_key, + schedule_date_from: newData?.schedule_date_from, + schedule_date_to: newData?.schedule_date_to, + + status: newData?.status, + creator_id: newData?.creator_id, + creator_name: newData?.creator_name, + editor_id: newData?.editor_id, + editor_name: newData?.editor_name, + created_at: newData?.created_at, + updated_at: newData?.updated_at, + description: description, + }; + + await this.service.create(payload as any); + this.logger.verbose( + `[SCHEDULING LOG] Change status data for ID: ${payload.data_id}`, + ); + } + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/handlers/data-scheduling-created.handler.ts b/src/modules/configuration/data-scheduling/domain/usecases/handlers/data-scheduling-created.handler.ts new file mode 100644 index 0000000..df851ee --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/handlers/data-scheduling-created.handler.ts @@ -0,0 +1,53 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { DataSchedulingCreatedEvent } from '../../entities/event/data-scheduling-created.event'; +import { + DataSchedulingLogEntity, + SCHEDULING_LOG_ACTION_ENUM, + SCHEDULING_LOG_TYPE_ENUM, +} from '../../entities/data-scheduling.entity'; +import { decryptionTotal } from '../../../infrastructure/helpers'; +import { Logger } from '@nestjs/common'; +import { DataSchedulingLogDataService } from '../../../data/services/data-scheduling-log-data.service'; + +@EventsHandler(DataSchedulingCreatedEvent) +export class DataSchedulingCreatedHandler + implements IEventHandler +{ + private readonly logger = new Logger(DataSchedulingCreatedHandler.name); + + constructor(private service: DataSchedulingLogDataService) {} + + async handle(event: DataSchedulingCreatedEvent) { + const data = event?.data?.data; + const totalPercentage = decryptionTotal(data?.indexing_key); + + const scheduleName = data?.name || 'a new schedule'; + const description = `
${data.creator_name} created ${scheduleName} schedule from ${data?.schedule_date_from} to ${data.schedule_date_to} with a total percentage of ${totalPercentage}%.
`; + + const payload: DataSchedulingLogEntity = { + type: SCHEDULING_LOG_TYPE_ENUM.DATA_SCHEDULING, + action: SCHEDULING_LOG_ACTION_ENUM.CREATE, + log_created_at: new Date().getTime(), + + data_id: data?.id, + name: data?.name, + indexing_key: data?.indexing_key, + schedule_date_from: data?.schedule_date_from, + schedule_date_to: data?.schedule_date_to, + + status: data?.status, + creator_id: data?.creator_id, + creator_name: data?.creator_name, + editor_id: data?.editor_id, + editor_name: data?.editor_name, + created_at: data?.created_at, + updated_at: data?.updated_at, + description: description, + }; + + await this.service.create(payload as any); + this.logger.verbose( + `[SCHEDULING LOG] Create data for ID: ${payload.data_id}`, + ); + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/handlers/data-scheduling-deleted.handler.ts b/src/modules/configuration/data-scheduling/domain/usecases/handlers/data-scheduling-deleted.handler.ts new file mode 100644 index 0000000..b173251 --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/handlers/data-scheduling-deleted.handler.ts @@ -0,0 +1,60 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { DataSchedulingDeletedEvent } from '../../entities/event/data-scheduling-deleted.event'; +import { + DataSchedulingLogEntity, + SCHEDULING_LOG_ACTION_ENUM, + SCHEDULING_LOG_TYPE_ENUM, +} from '../../entities/data-scheduling.entity'; +import { Logger } from '@nestjs/common'; +import { DataSchedulingLogDataService } from '../../../data/services/data-scheduling-log-data.service'; +import { decryptionTotal } from '../../../infrastructure/helpers'; + +@EventsHandler(DataSchedulingDeletedEvent) +export class DataSchedulingDeletedHandler + implements IEventHandler +{ + private readonly logger = new Logger(DataSchedulingDeletedHandler.name); + + constructor(private service: DataSchedulingLogDataService) {} + + async handle(event: DataSchedulingDeletedEvent) { + const deletedData = event?.data?.data; + const user = event?.data?.user; + + const deleterName = + user?.name || + deletedData?.editor_name || + deletedData?.creator_name || + 'System'; + + const scheduleName = deletedData?.name || 'an item'; + const totalPercentage = decryptionTotal(deletedData?.indexing_key); + const description = `
${deleterName} deleted schedule: ${scheduleName} (${totalPercentage}%) schedule from ${deletedData?.schedule_date_from} to ${deletedData.schedule_date_to}.
`; + + const payload: DataSchedulingLogEntity = { + type: SCHEDULING_LOG_TYPE_ENUM.DATA_SCHEDULING, + action: SCHEDULING_LOG_ACTION_ENUM.DELETE, + log_created_at: new Date().getTime(), + + data_id: deletedData?.id, + name: deletedData?.name, + indexing_key: deletedData?.indexing_key, + schedule_date_from: deletedData?.schedule_date_from, + schedule_date_to: deletedData?.schedule_date_to, + status: deletedData?.status, + + creator_id: deletedData?.creator_id, + creator_name: deletedData?.creator_name, + editor_id: deletedData?.editor_id, + editor_name: deletedData?.editor_name, + created_at: deletedData?.created_at, + updated_at: deletedData?.updated_at, + description: description, + }; + + await this.service.create(payload as any); + this.logger.verbose( + `[SCHEDULING LOG] Delete data for ID: ${payload.data_id}`, + ); + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/handlers/data-scheduling-updated.handler.ts b/src/modules/configuration/data-scheduling/domain/usecases/handlers/data-scheduling-updated.handler.ts new file mode 100644 index 0000000..b945d97 --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/handlers/data-scheduling-updated.handler.ts @@ -0,0 +1,157 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { DataSchedulingUpdatedEvent } from '../../entities/event/data-scheduling-updated.event'; +import { + DataSchedulingLogEntity, + SCHEDULING_LOG_ACTION_ENUM, + SCHEDULING_LOG_TYPE_ENUM, +} from '../../entities/data-scheduling.entity'; +import { + decryptionTotal, + encryptionTotal, +} from '../../../infrastructure/helpers'; +import { Logger } from '@nestjs/common'; +import { DataSchedulingLogDataService } from '../../../data/services/data-scheduling-log-data.service'; + +@EventsHandler(DataSchedulingUpdatedEvent) +export class DataSchedulingUpdatedHandler + implements IEventHandler +{ + private readonly logger = new Logger(DataSchedulingUpdatedHandler.name); + + constructor(private service: DataSchedulingLogDataService) {} + + // Map for readable labels + private readonly labelMap: { [key: string]: string } = { + name: 'Name', + indexing_key: 'Total Data', + schedule_date_from: 'Start Date', + schedule_date_to: 'End Date', + }; + + // Relevant keys for comparing changes + private readonly keysToCompare: string[] = [ + 'name', + 'indexing_key', + 'schedule_date_from', + 'schedule_date_to', + ]; + + async handle(event: DataSchedulingUpdatedEvent) { + const oldData = event?.data?.old; + // Decrypt oldData.indexing_key here before comparison + if (oldData?.indexing_key !== undefined && oldData?.indexing_key !== null) { + oldData.indexing_key = decryptionTotal(oldData.indexing_key); + } + + const newData = event?.data?.data; + // Decrypt newData.indexing_key here before comparison + if (newData?.indexing_key !== undefined && newData?.indexing_key !== null) { + newData.indexing_key = decryptionTotal(newData.indexing_key); + } + + const changingData = this.getChangingData(oldData, newData); + const description = this.generateDescription( + oldData, + newData, + changingData, + ); + + const payload: DataSchedulingLogEntity = { + type: SCHEDULING_LOG_TYPE_ENUM.DATA_SCHEDULING, + action: SCHEDULING_LOG_ACTION_ENUM.UPDATE, + log_created_at: new Date().getTime(), + + data_id: newData?.id, + name: newData?.name, + indexing_key: encryptionTotal(newData?.indexing_key), + schedule_date_from: newData?.schedule_date_from, + schedule_date_to: newData?.schedule_date_to, + + status: newData?.status, + creator_id: newData?.creator_id, + creator_name: newData?.creator_name, + editor_id: newData?.editor_id, + editor_name: newData?.editor_name, + created_at: newData?.created_at, + updated_at: newData?.updated_at, + description: description, + }; + + await this.service.create(payload as any); + this.logger.verbose( + `[SCHEDULING LOG] Update data for ID: ${payload.data_id}`, + ); + } + + /** + * Compares old and new data to find changes. + * @param oldData Data before the change. + * @param newData Data after the change. + * @returns An object containing the old and new changed data. + */ + private getChangingData(oldData: any, newData: any): { old: any; new: any } { + const changingData: { old: any; new: any } = { old: {}, new: {} }; + + this.keysToCompare.forEach((key) => { + // Ensure comparisons are made on decrypted values if decryption happens before this + if (oldData?.[key] !== newData?.[key]) { + changingData.old[key] = oldData?.[key]; + changingData.new[key] = newData?.[key]; + } + }); + + return changingData; + } + + /** + * Generates an HTML description string based on data changes. + * Includes the name from oldData for identification. + * @param oldData Old data, used to get the name of the item. + * @param newData New data containing editor information. + * @param changingData An object containing the changed data. + * @returns The HTML string of the change description. + */ + private generateDescription( + oldData: any, + newData: any, + changingData: { old: any; new: any }, + ): string { + const editorName = newData.editor_name || 'System'; + const itemName = oldData?.name || 'an item'; + + const totalPercentageOld = oldData?.indexing_key; + const totalPercentageNew = newData?.indexing_key; + const isTotalSame = totalPercentageOld === totalPercentageNew; + const labelName = `${ + isTotalSame + ? `${totalPercentageNew}%` + : `${totalPercentageOld}% to ${totalPercentageNew}%` + }`; + + let description = `
${editorName} has updated schedule for ${itemName} (${labelName}) schedule from ${newData?.schedule_date_from} to ${newData.schedule_date_to}.
`; + + if (Object.keys(changingData.old).length > 0) { + description += `Change details:
    `; + for (const key in changingData.old) { + if (Object.prototype.hasOwnProperty.call(changingData.old, key)) { + const label = this.labelMap[key] || key; + let oldValue = changingData.old[key] || 'empty'; + let newValue = changingData.new[key] || 'empty'; + + // Add '%' suffix if the key is 'indexing_key' + if (key === 'indexing_key') { + oldValue = `${oldValue}%`; + newValue = `${newValue}%`; + } + + description += `
  • ${label} changed from ${oldValue} to ${newValue}.
  • `; + } + } + description += `
`; + } else { + description += ` No significant data detail changes.

`; + } + + return description; + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/managers/active-data-scheduling.manager.ts b/src/modules/configuration/data-scheduling/domain/usecases/managers/active-data-scheduling.manager.ts new file mode 100644 index 0000000..66fdfee --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/managers/active-data-scheduling.manager.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager'; +import { DataSchedulingEntity } from '../../entities/data-scheduling.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { DataSchedulingModel } from '../../../data/models/data-scheduling.model'; +import { DataSchedulingChangeStatusEvent } from '../../entities/event/data-scheduling-change-status.event'; + +@Injectable() +export class ActiveDataSchedulingManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.name}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return DataSchedulingModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: DataSchedulingChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/managers/batch-active-data-scheduling.manager.ts b/src/modules/configuration/data-scheduling/domain/usecases/managers/batch-active-data-scheduling.manager.ts new file mode 100644 index 0000000..d1ad181 --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/managers/batch-active-data-scheduling.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { DataSchedulingEntity } from '../../entities/data-scheduling.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { DataSchedulingModel } from '../../../data/models/data-scheduling.model'; +import { DataSchedulingChangeStatusEvent } from '../../entities/event/data-scheduling-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchActiveDataSchedulingManager extends BaseBatchUpdateStatusManager { + validateData(data: DataSchedulingEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return DataSchedulingModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: DataSchedulingChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/managers/batch-confirm-data-scheduling.manager.ts b/src/modules/configuration/data-scheduling/domain/usecases/managers/batch-confirm-data-scheduling.manager.ts new file mode 100644 index 0000000..51ba35f --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/managers/batch-confirm-data-scheduling.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { DataSchedulingEntity } from '../../entities/data-scheduling.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { DataSchedulingModel } from '../../../data/models/data-scheduling.model'; +import { DataSchedulingChangeStatusEvent } from '../../entities/event/data-scheduling-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchConfirmDataSchedulingManager extends BaseBatchUpdateStatusManager { + validateData(data: DataSchedulingEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return DataSchedulingModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: DataSchedulingChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/managers/batch-delete-data-scheduling.manager.ts b/src/modules/configuration/data-scheduling/domain/usecases/managers/batch-delete-data-scheduling.manager.ts new file mode 100644 index 0000000..30b3cae --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/managers/batch-delete-data-scheduling.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchDeleteManager } from 'src/core/modules/domain/usecase/managers/base-batch-delete.manager'; +import { DataSchedulingEntity } from '../../entities/data-scheduling.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { DataSchedulingModel } from '../../../data/models/data-scheduling.model'; +import { DataSchedulingDeletedEvent } from '../../entities/event/data-scheduling-deleted.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchDeleteDataSchedulingManager extends BaseBatchDeleteManager { + async beforeProcess(): Promise { + return; + } + + async validateData(data: DataSchedulingEntity): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return DataSchedulingModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: DataSchedulingDeletedEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/managers/batch-inactive-data-scheduling.manager.ts b/src/modules/configuration/data-scheduling/domain/usecases/managers/batch-inactive-data-scheduling.manager.ts new file mode 100644 index 0000000..d74b1fc --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/managers/batch-inactive-data-scheduling.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { DataSchedulingEntity } from '../../entities/data-scheduling.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { DataSchedulingModel } from '../../../data/models/data-scheduling.model'; +import { DataSchedulingChangeStatusEvent } from '../../entities/event/data-scheduling-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchInactiveDataSchedulingManager extends BaseBatchUpdateStatusManager { + validateData(data: DataSchedulingEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return DataSchedulingModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: DataSchedulingChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/managers/confirm-data-scheduling.manager.ts b/src/modules/configuration/data-scheduling/domain/usecases/managers/confirm-data-scheduling.manager.ts new file mode 100644 index 0000000..20abaa7 --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/managers/confirm-data-scheduling.manager.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager'; +import { DataSchedulingEntity } from '../../entities/data-scheduling.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { DataSchedulingModel } from '../../../data/models/data-scheduling.model'; +import { DataSchedulingChangeStatusEvent } from '../../entities/event/data-scheduling-change-status.event'; + +@Injectable() +export class ConfirmDataSchedulingManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.name}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return DataSchedulingModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: DataSchedulingChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/managers/create-data-scheduling.manager.ts b/src/modules/configuration/data-scheduling/domain/usecases/managers/create-data-scheduling.manager.ts new file mode 100644 index 0000000..1a952dd --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/managers/create-data-scheduling.manager.ts @@ -0,0 +1,88 @@ +import { Injectable } from '@nestjs/common'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { DataSchedulingEntity } from '../../entities/data-scheduling.entity'; +import { DataSchedulingModel } from '../../../data/models/data-scheduling.model'; +import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager'; +import { DataSchedulingCreatedEvent } from '../../entities/event/data-scheduling-created.event'; +import { encryptionTotal } from '../../../infrastructure/helpers'; +import * as moment from 'moment'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class CreateDataSchedulingManager extends BaseCreateManager { + async beforeProcess(): Promise { + const total = this.data.indexing_key; + + if (total > 100) { + throw new Error('Maksimal nilai total adalah 100.'); + } + + const queryBuilder = this.dataService + .getRepository() + .createQueryBuilder(this.tableName); + + const overlapping = await queryBuilder + .where(`${this.tableName}.schedule_date_from <= :schedule_date_to`, { + schedule_date_to: this.data.schedule_date_to, + }) + .andWhere(`${this.tableName}.schedule_date_to >= :schedule_date_from`, { + schedule_date_from: this.data.schedule_date_from, + }) + .getOne(); + + if (this.data) { + Object.assign(this.data, { + indexing_key: encryptionTotal(total), + status: STATUS.ACTIVE, + }); + } + + // Validation date + if (overlapping) { + throw new Error('Tanggal yang dimasukkan beririsan dengan data lain.'); + } else if (this.data.schedule_date_from && this.data.schedule_date_to) { + const start_time = moment(this.data.schedule_date_from); + const end_time = moment(this.data.schedule_date_to); + + if (end_time.isBefore(start_time)) { + throw new Error('Tanggal akhir harus lebih besar dari tanggal mulai.'); + } + return; + } + + return; + } + + async afterProcess(): Promise { + return; + } + + async generateConfig(): Promise { + // TODO: Implement logic here + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return [{ column: 'name' }]; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: DataSchedulingCreatedEvent, + data: this.data, + }, + ]; + } + + get entityTarget(): any { + return DataSchedulingModel; + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/managers/data-scheduling-default.manager.ts b/src/modules/configuration/data-scheduling/domain/usecases/managers/data-scheduling-default.manager.ts new file mode 100644 index 0000000..ceac80f --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/managers/data-scheduling-default.manager.ts @@ -0,0 +1,119 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { UserProvider, UsersSession } from 'src/core/sessions'; +import { BLANK_USER } from 'src/core/strings/constants/base.constants'; +import { EditDataSchedulingDefaultDto } from '../../../infrastructure/dto/data-scheduling.dto'; +import { + DataSchedulingDefaultEntity, + DataSchedulingLogEntity, + SCHEDULING_LOG_ACTION_ENUM, + SCHEDULING_LOG_TYPE_ENUM, +} from '../../entities/data-scheduling.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { DataSchedulingDefaultModel } from '../../../data/models/data-scheduling-default.model'; +import { Repository } from 'typeorm'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { SelectQueryBuilder } from 'typeorm'; +import { EventBus } from '@nestjs/cqrs'; +import { DataSchedulingChangeStatusEvent } from '../../entities/event/data-scheduling-change-status.event'; +import { DataSchedulingLogModel } from '../../../data/models/data-scheduling-log.model'; + +@Injectable() +export class DataSchedulingManager { + @Inject() + protected userProvider: UserProvider; + + constructor( + private eventBus: EventBus, + + @InjectRepository(DataSchedulingDefaultModel) + private repository: Repository, + + @InjectRepository(DataSchedulingLogModel) + private repositoryLog: Repository, + ) {} + + private getUser(): UsersSession { + try { + return this.userProvider?.user ?? BLANK_USER; + } catch (error) { + return BLANK_USER; + } + } + + get tableName(): string { + return TABLE_NAME.DATA_SCHEDULING_DEFAULT; + } + + private queryBuilder(): SelectQueryBuilder { + return this.repository.createQueryBuilder(this.tableName); + } + + async update( + body: EditDataSchedulingDefaultDto, + ): Promise { + if (body.default_value > 100) { + throw new Error('Value tidak boleh lebih dari 100.'); + } + + const userData = this.getUser(); + const dateNow = new Date().getTime(); + const existData = await this.queryBuilder().getOne(); + + const payload: DataSchedulingDefaultEntity = { + id: existData?.id, + default_value: body.default_value, + creator_id: userData.id as any, + creator_name: userData.name, + editor_id: userData.id as any, + editor_name: userData.name, + created_at: dateNow, + updated_at: dateNow, + }; + + const saveData = await this.repository.save(payload); + + if (existData?.default_value !== saveData?.default_value) { + const description = existData?.id + ? `
${saveData.editor_name} changed the Default Percentage setting from ${existData.default_value}% to ${saveData.default_value}%.
` + : `
${saveData.creator_name} created the Default Percentage setting with a value of ${saveData.default_value}%.
`; + + const logPayload: DataSchedulingLogEntity = { + type: SCHEDULING_LOG_TYPE_ENUM.DEFAULT_PERCENTAGE, + action: existData?.id + ? SCHEDULING_LOG_ACTION_ENUM.UPDATE + : SCHEDULING_LOG_ACTION_ENUM.CREATE, + log_created_at: new Date().getTime(), + status: undefined, + + data_id: saveData?.id, + creator_id: saveData?.creator_id, + creator_name: saveData?.creator_name, + editor_id: saveData?.editor_id, + editor_name: saveData?.editor_name, + created_at: saveData?.created_at, + updated_at: saveData?.updated_at, + default_value: saveData?.default_value, + description: description, + }; + + await this.repositoryLog.save(logPayload as any); + } + await this.publishEventUpdates(); + return saveData; + } + + async getData() { + return this.queryBuilder().getOne(); + } + + async publishEventUpdates() { + await this.eventBus.publish( + new DataSchedulingChangeStatusEvent({ data: null } as any), + ); + } + + async setupActiveScheduling() { + await this.publishEventUpdates(); + return { message: 'Success setup transaction schedule.' }; + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/managers/delete-data-scheduling.manager.ts b/src/modules/configuration/data-scheduling/domain/usecases/managers/delete-data-scheduling.manager.ts new file mode 100644 index 0000000..05f9b63 --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/managers/delete-data-scheduling.manager.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDeleteManager } from 'src/core/modules/domain/usecase/managers/base-delete.manager'; +import { DataSchedulingEntity } from '../../entities/data-scheduling.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { DataSchedulingModel } from '../../../data/models/data-scheduling.model'; +import { DataSchedulingDeletedEvent } from '../../entities/event/data-scheduling-deleted.event'; + +@Injectable() +export class DeleteDataSchedulingManager extends BaseDeleteManager { + getResult(): string { + return `Success`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return DataSchedulingModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: DataSchedulingDeletedEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/managers/detail-data-scheduling.manager.ts b/src/modules/configuration/data-scheduling/domain/usecases/managers/detail-data-scheduling.manager.ts new file mode 100644 index 0000000..c3a74f4 --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/managers/detail-data-scheduling.manager.ts @@ -0,0 +1,59 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { BaseDetailManager } from 'src/core/modules/domain/usecase/managers/base-detail.manager'; +import { DataSchedulingEntity } from '../../entities/data-scheduling.entity'; +import { RelationParam } from 'src/core/modules/domain/entities/base-filter.entity'; +import { decryptionTotal } from '../../../infrastructure/helpers'; + +@Injectable() +export class DetailDataSchedulingManager extends BaseDetailManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + joinRelations: [], + selectRelations: [], + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.status`, + `${this.tableName}.name`, + `${this.tableName}.indexing_key`, + `${this.tableName}.schedule_date_from`, + `${this.tableName}.schedule_date_to`, + `${this.tableName}.created_at`, + `${this.tableName}.creator_name`, + `${this.tableName}.updated_at`, + `${this.tableName}.editor_name`, + ]; + } + + get setFindProperties(): any { + return { + id: this.dataId, + }; + } + + getResult(): DataSchedulingEntity { + if (!this.result) throw new NotFoundException('Data not found.'); + + const total = decryptionTotal(this.result.indexing_key as string); + return { + ...this.result, + indexing_key: total, + }; + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/managers/inactive-data-scheduling.manager.ts b/src/modules/configuration/data-scheduling/domain/usecases/managers/inactive-data-scheduling.manager.ts new file mode 100644 index 0000000..0b5c9ed --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/managers/inactive-data-scheduling.manager.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager'; +import { DataSchedulingEntity } from '../../entities/data-scheduling.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { DataSchedulingModel } from '../../../data/models/data-scheduling.model'; +import { DataSchedulingChangeStatusEvent } from '../../entities/event/data-scheduling-change-status.event'; + +@Injectable() +export class InactiveDataSchedulingManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success inactive data ${this.result.name}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return DataSchedulingModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: DataSchedulingChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/managers/index-data-scheduling-log.manager.ts b/src/modules/configuration/data-scheduling/domain/usecases/managers/index-data-scheduling-log.manager.ts new file mode 100644 index 0000000..33a8eac --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/managers/index-data-scheduling-log.manager.ts @@ -0,0 +1,116 @@ +import { Injectable } from '@nestjs/common'; +import { BaseIndexManager } from 'src/core/modules/domain/usecase/managers/base-index.manager'; +import { DataSchedulingLogEntity } from '../../entities/data-scheduling.entity'; +import { SelectQueryBuilder } from 'typeorm'; +import { + Param, + RelationParam, +} from 'src/core/modules/domain/entities/base-filter.entity'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { decryptionTotal } from '../../../infrastructure/helpers'; + +@Injectable() +export class IndexDataSchedulingLogManager extends BaseIndexManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + joinRelations: [], + selectRelations: [], + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.type`, + `${this.tableName}.action`, + `${this.tableName}.log_created_at`, + `${this.tableName}.data_id`, + `${this.tableName}.default_value`, + `${this.tableName}.description`, + + `${this.tableName}.status`, + `${this.tableName}.name`, + `${this.tableName}.indexing_key`, + `${this.tableName}.schedule_date_from`, + `${this.tableName}.schedule_date_to`, + `${this.tableName}.created_at`, + `${this.tableName}.creator_name`, + `${this.tableName}.updated_at`, + `${this.tableName}.editor_name`, + ]; + } + + get specificFilter(): Param[] { + return [ + { + cols: `${this.tableName}.name`, + data: this.filterParam.names, + }, + ]; + } + + setQueryFilter( + queryBuilder: SelectQueryBuilder, + ): SelectQueryBuilder { + if (this.filterParam.schedule_date_from) { + const dateFrom = this.filterParam.schedule_date_from; + queryBuilder.andWhere('schedule_date_from >= :dateFrom', { + dateFrom: dateFrom, + }); + } + + if (this.filterParam.schedule_date_to) { + const dateTo = this.filterParam.schedule_date_to; + queryBuilder.andWhere('schedule_date_to <= :dateTo', { + dateTo: dateTo, + }); + } + + if (this.filterParam.log_created_from && this.filterParam.log_created_to) { + const dateFrom = this.filterParam.log_created_from; + const dateTo = this.filterParam.log_created_to; + + queryBuilder.andWhere('log_created_at BETWEEN :dateFrom AND :dateTo', { + dateFrom: dateFrom, + dateTo: dateTo, + }); + } else if (this.filterParam.log_created_from) { + const dateFrom = this.filterParam.log_created_from; + queryBuilder.andWhere('log_created_at >= :dateFrom', { + dateFrom: dateFrom, + }); + } else if (this.filterParam.log_created_to) { + const dateTo = this.filterParam.log_created_to; + queryBuilder.andWhere('log_created_at <= :dateTo', { + dateTo: dateTo, + }); + } + + return queryBuilder; + } + + getResult(): PaginationResponse { + const data = this.result.data; + + return { + ...this.result, + data: data.map((item) => { + const total = decryptionTotal(item.indexing_key as string); + return { ...item, indexing_key: total }; + }), + }; + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/managers/index-data-scheduling.manager.ts b/src/modules/configuration/data-scheduling/domain/usecases/managers/index-data-scheduling.manager.ts new file mode 100644 index 0000000..3acd015 --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/managers/index-data-scheduling.manager.ts @@ -0,0 +1,88 @@ +import { Injectable } from '@nestjs/common'; +import { BaseIndexManager } from 'src/core/modules/domain/usecase/managers/base-index.manager'; +import { DataSchedulingEntity } from '../../entities/data-scheduling.entity'; +import { SelectQueryBuilder } from 'typeorm'; +import { + Param, + RelationParam, +} from 'src/core/modules/domain/entities/base-filter.entity'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { decryptionTotal } from '../../../infrastructure/helpers'; + +@Injectable() +export class IndexDataSchedulingManager extends BaseIndexManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + joinRelations: [], + selectRelations: [], + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.status`, + `${this.tableName}.name`, + `${this.tableName}.indexing_key`, + `${this.tableName}.schedule_date_from`, + `${this.tableName}.schedule_date_to`, + `${this.tableName}.created_at`, + `${this.tableName}.creator_name`, + `${this.tableName}.updated_at`, + `${this.tableName}.editor_name`, + ]; + } + + get specificFilter(): Param[] { + return [ + { + cols: `${this.tableName}.name`, + data: this.filterParam.names, + }, + ]; + } + + setQueryFilter( + queryBuilder: SelectQueryBuilder, + ): SelectQueryBuilder { + if (this.filterParam.schedule_date_from) { + const dateFrom = this.filterParam.schedule_date_from; + queryBuilder.andWhere('schedule_date_from >= :dateFrom', { + dateFrom: dateFrom, + }); + } + + if (this.filterParam.schedule_date_to) { + const dateTo = this.filterParam.schedule_date_to; + queryBuilder.andWhere('schedule_date_to <= :dateTo', { + dateTo: dateTo, + }); + } + return queryBuilder; + } + + getResult(): PaginationResponse { + const data = this.result.data; + + return { + ...this.result, + data: data.map((item) => { + const total = decryptionTotal(item.indexing_key as string); + return { ...item, indexing_key: total }; + }), + }; + } +} diff --git a/src/modules/configuration/data-scheduling/domain/usecases/managers/update-data-scheduling.manager.ts b/src/modules/configuration/data-scheduling/domain/usecases/managers/update-data-scheduling.manager.ts new file mode 100644 index 0000000..ff61086 --- /dev/null +++ b/src/modules/configuration/data-scheduling/domain/usecases/managers/update-data-scheduling.manager.ts @@ -0,0 +1,76 @@ +import { Injectable } from '@nestjs/common'; +import { BaseUpdateManager } from 'src/core/modules/domain/usecase/managers/base-update.manager'; +import { DataSchedulingEntity } from '../../entities/data-scheduling.entity'; +import { DataSchedulingModel } from '../../../data/models/data-scheduling.model'; +import { DataSchedulingUpdatedEvent } from '../../entities/event/data-scheduling-updated.event'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { encryptionTotal } from '../../../infrastructure/helpers'; + +@Injectable() +export class UpdateDataSchedulingManager extends BaseUpdateManager { + async validateProcess(): Promise { + const total = this.data.indexing_key; + + if (total > 100) { + throw new Error('Maksimal nilai total adalah 100.'); + } + + const queryBuilder = this.dataService + .getRepository() + .createQueryBuilder(this.tableName); + + const overlapping = await queryBuilder + .where(`${this.tableName}.schedule_date_from <= :schedule_date_to`, { + schedule_date_to: this.data.schedule_date_to, + }) + .andWhere(`${this.tableName}.schedule_date_to >= :schedule_date_from`, { + schedule_date_from: this.data.schedule_date_from, + }) + .andWhere(`${this.tableName}.id != :id`, { id: this.dataId ?? null }) + .getOne(); + + // Validation date + if (overlapping) { + throw new Error('Tanggal yang dimasukkan tidak boleh sama.'); + } else { + //Encryption total data + Object.assign(this.data, { + indexing_key: encryptionTotal(total), + }); + } + + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return [{ column: 'name' }]; + } + + get entityTarget(): any { + return DataSchedulingModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: DataSchedulingUpdatedEvent, + }, + ]; + } +} diff --git a/src/modules/configuration/data-scheduling/infrastructure/data-scheduling-data.controller.ts b/src/modules/configuration/data-scheduling/infrastructure/data-scheduling-data.controller.ts new file mode 100644 index 0000000..bafdfae --- /dev/null +++ b/src/modules/configuration/data-scheduling/infrastructure/data-scheduling-data.controller.ts @@ -0,0 +1,148 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Patch, + Post, + Put, + UseGuards, +} from '@nestjs/common'; +import { DataSchedulingDataOrchestrator } from '../domain/usecases/data-scheduling-data.orchestrator'; +import { + CreateDataSchedulingDto, + EditDataSchedulingDto, + EditDataSchedulingDefaultDto, + DeleteDataSchedulingLogDto, +} from './dto/data-scheduling.dto'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { + DataSchedulingDefaultEntity, + DataSchedulingEntity, +} from '../domain/entities/data-scheduling.entity'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto'; +import { ExcludePrivilege, Public } from 'src/core/guards'; +import { SetupSchedulingGuard } from './guards/setup-scheduling.guard'; +import { DataSchedulingManager } from '../domain/usecases/managers/data-scheduling-default.manager'; +import { DataSchedulingLogDataService } from '../data/services/data-scheduling-log-data.service'; + +@ApiTags(`${MODULE_NAME.DATA_SCHEDULING.split('-').join(' ')} - data`) +@Controller(`v1/${MODULE_NAME.DATA_SCHEDULING}`) +@Public(false) +@ApiBearerAuth('JWT') +export class DataSchedulingDataController { + constructor(private orchestrator: DataSchedulingDataOrchestrator) {} + + @Post() + async create( + @Body() data: CreateDataSchedulingDto, + ): Promise { + return await this.orchestrator.create(data); + } + + @Put('/batch-delete') + async batchDeleted(@Body() body: BatchIdsDto): Promise { + return await this.orchestrator.batchDelete(body.ids); + } + + @Patch(':id/active') + async active(@Param('id') dataId: string): Promise { + return await this.orchestrator.active(dataId); + } + + @Put('/batch-active') + async batchActive(@Body() body: BatchIdsDto): Promise { + return await this.orchestrator.batchActive(body.ids); + } + + @Patch(':id/confirm') + async confirm(@Param('id') dataId: string): Promise { + return await this.orchestrator.confirm(dataId); + } + + @Put('/batch-confirm') + async batchConfirm(@Body() body: BatchIdsDto): Promise { + return await this.orchestrator.batchConfirm(body.ids); + } + + @Patch(':id/inactive') + async inactive(@Param('id') dataId: string): Promise { + return await this.orchestrator.inactive(dataId); + } + + @Put('/batch-inactive') + async batchInactive(@Body() body: BatchIdsDto): Promise { + return await this.orchestrator.batchInactive(body.ids); + } + + @Put(':id') + async update( + @Param('id') dataId: string, + @Body() data: EditDataSchedulingDto, + ): Promise { + return await this.orchestrator.update(dataId, data); + } + + @Delete(':id') + async delete(@Param('id') dataId: string): Promise { + return await this.orchestrator.delete(dataId); + } +} + +@ApiTags( + `${MODULE_NAME.DATA_SCHEDULING_DEFAULT.split('-').join(' ')} default - Data`, +) +@Controller(`v1/${MODULE_NAME.DATA_SCHEDULING_DEFAULT}`) +@Public(false) +@ApiBearerAuth('JWT') +export class DataSchedulingDefaultController { + constructor(private manager: DataSchedulingManager) {} + @Post() + async create( + @Body() data: EditDataSchedulingDefaultDto, + ): Promise { + return await this.manager.update(data); + } + + @Get() + async get(): Promise { + return await this.manager.getData(); + } +} + +@ApiTags( + `${MODULE_NAME.DATA_SCHEDULING_SETUP.split('-').join(' ')} setup - Data`, +) +@Controller(`v1/${MODULE_NAME.DATA_SCHEDULING_SETUP}`) +@Public(true) +@ApiBearerAuth('JWT') +export class DataSchedulingSetupController { + constructor(private manager: DataSchedulingManager) {} + + @Post() + @ExcludePrivilege() + @UseGuards(SetupSchedulingGuard) + async setup(): Promise<{ message: string }> { + return this.manager.setupActiveScheduling(); + } +} + +@ApiTags(`${MODULE_NAME.DATA_SCHEDULING_LOG.split('-').join(' ')} log - Data`) +@Controller(`v1/${MODULE_NAME.DATA_SCHEDULING_LOG}`) +@Public(true) +@ApiBearerAuth('JWT') +export class DataSchedulingDataLogController { + constructor(private service: DataSchedulingLogDataService) {} + + @Post('delete-range') + @ExcludePrivilege() + @UseGuards(SetupSchedulingGuard) + async setup( + @Body() data: DeleteDataSchedulingLogDto, + ): Promise<{ message: string }> { + return this.service.deleteRange(data.log_created_from, data.log_created_to); + } +} diff --git a/src/modules/configuration/data-scheduling/infrastructure/data-scheduling-read.controller.ts b/src/modules/configuration/data-scheduling/infrastructure/data-scheduling-read.controller.ts new file mode 100644 index 0000000..552045f --- /dev/null +++ b/src/modules/configuration/data-scheduling/infrastructure/data-scheduling-read.controller.ts @@ -0,0 +1,53 @@ +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { + FilterDataSchedulingDto, + FilterDataSchedulingLogDto, +} from './dto/filter-data-scheduling.dto'; +import { Pagination } from 'src/core/response'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { + DataSchedulingEntity, + DataSchedulingLogEntity, +} from '../domain/entities/data-scheduling.entity'; +import { DataSchedulingReadOrchestrator } from '../domain/usecases/data-scheduling-read.orchestrator'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { ExcludePrivilege, Public } from 'src/core/guards'; + +@ApiTags(`${MODULE_NAME.DATA_SCHEDULING.split('-').join(' ')} - read`) +@Controller(`v1/${MODULE_NAME.DATA_SCHEDULING}`) +@Public(false) +@ApiBearerAuth('JWT') +export class DataSchedulingReadController { + constructor(private orchestrator: DataSchedulingReadOrchestrator) {} + + @Get() + @Pagination() + async index( + @Query() params: FilterDataSchedulingDto, + ): Promise> { + return await this.orchestrator.index(params); + } + + @Get(':id') + async detail(@Param('id') id: string): Promise { + return await this.orchestrator.detail(id); + } +} + +@ApiTags(`${MODULE_NAME.DATA_SCHEDULING_LOG.split('-').join(' ')} - read`) +@Controller(`v1/${MODULE_NAME.DATA_SCHEDULING_LOG}`) +@Public(false) +@ApiBearerAuth('JWT') +export class DataSchedulingLogReadController { + constructor(private orchestrator: DataSchedulingReadOrchestrator) {} + + @Get() + @Pagination() + @ExcludePrivilege() + async index( + @Query() params: FilterDataSchedulingLogDto, + ): Promise> { + return await this.orchestrator.indexLog(params); + } +} diff --git a/src/modules/configuration/data-scheduling/infrastructure/dto/data-scheduling.dto.ts b/src/modules/configuration/data-scheduling/infrastructure/dto/data-scheduling.dto.ts new file mode 100644 index 0000000..ab1f7b0 --- /dev/null +++ b/src/modules/configuration/data-scheduling/infrastructure/dto/data-scheduling.dto.ts @@ -0,0 +1,98 @@ +import { BaseStatusDto } from 'src/core/modules/infrastructure/dto/base-status.dto'; +import { + DataSchedulingDefaultEntity, + DataSchedulingEntity, +} from '../../domain/entities/data-scheduling.entity'; +import { IsString, ValidateIf } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; +import { BaseCoreDto } from 'src/core/modules/infrastructure/dto/base-core.dto'; +import { Transform } from 'class-transformer'; +import { BaseDto } from 'src/core/modules/infrastructure/dto/base.dto'; + +export class CreateDataSchedulingDto + extends BaseStatusDto + implements DataSchedulingEntity +{ + @ApiProperty({ name: 'name', required: true, example: 'Morning' }) + @IsString() + name: string; + + @ApiProperty({ type: 'integer', required: true, example: 80 }) + @Transform((body) => { + return typeof body.value == 'string' ? Number(body.value) : body.value; + }) + @ValidateIf((body) => body.indexing_key) + indexing_key: number; + + @ApiProperty({ + type: Date, + required: true, + example: '2024-01-01', + }) + schedule_date_from: Date; + + @ApiProperty({ + type: Date, + required: true, + example: '2024-01-02', + }) + schedule_date_to: Date; +} + +export class EditDataSchedulingDto + extends BaseStatusDto + implements DataSchedulingEntity +{ + @ApiProperty({ name: 'name', example: 'Morning' }) + @IsString() + @ValidateIf((body) => body.name) + name: string; + + @ApiProperty({ type: 'integer', required: true, example: 80 }) + @Transform((body) => { + return typeof body.value == 'string' ? Number(body.value) : body.value; + }) + @ValidateIf((body) => body.indexing_key) + indexing_key: number; + + @ApiProperty({ + type: Date, + required: true, + example: '2025-01-01', + }) + schedule_date_from: Date; + + @ApiProperty({ + type: Date, + required: true, + example: '2025-01-02', + }) + schedule_date_to: Date; +} + +export class EditDataSchedulingDefaultDto + extends BaseDto + implements DataSchedulingDefaultEntity +{ + @ApiProperty({ type: 'integer', required: true }) + @Transform((body) => { + return typeof body.value == 'string' ? Number(body.value) : body.value; + }) + @ValidateIf((body) => body.default_value) + default_value: number; +} + +export class SetupDataSchedulingDto { + // @ApiProperty({ type: 'string', required: true, example: '2025-01-01' }) + // date: Date; +} + +export class DeleteDataSchedulingLogDto { + @ApiProperty({ type: Number, required: true }) + @ValidateIf((body) => body.log_created_from) + log_created_from: number; + + @ApiProperty({ type: Number, required: true }) + @ValidateIf((body) => body.log_created_to) + log_created_to: number; +} diff --git a/src/modules/configuration/data-scheduling/infrastructure/dto/filter-data-scheduling.dto.ts b/src/modules/configuration/data-scheduling/infrastructure/dto/filter-data-scheduling.dto.ts new file mode 100644 index 0000000..628cd11 --- /dev/null +++ b/src/modules/configuration/data-scheduling/infrastructure/dto/filter-data-scheduling.dto.ts @@ -0,0 +1,44 @@ +import { BaseFilterDto } from 'src/core/modules/infrastructure/dto/base-filter.dto'; +import { FilterDataSchedulingEntity } from '../../domain/entities/filter-data-scheduling.entity'; +import { ApiProperty } from '@nestjs/swagger'; +import { ValidateIf } from 'class-validator'; + +export class FilterDataSchedulingDto + extends BaseFilterDto + implements FilterDataSchedulingEntity +{ + @ApiProperty({ type: 'string', required: false }) + @ValidateIf((body) => body.schedule_date_from) + schedule_date_from: Date; + + @ApiProperty({ type: 'string', required: false }) + @ValidateIf((body) => body.schedule_date_to) + schedule_date_to: Date; +} + +export class FilterDataSchedulingLogDto + extends BaseFilterDto + implements FilterDataSchedulingEntity +{ + @ApiProperty({ type: 'string', required: false }) + @ValidateIf((body) => body.schedule_date_from) + schedule_date_from: Date; + + @ApiProperty({ type: 'string', required: false }) + @ValidateIf((body) => body.schedule_date_to) + schedule_date_to: Date; + + @ApiProperty({ type: Number, required: false }) + @ValidateIf((body) => body.log_created_from) + log_created_from: number; + + @ApiProperty({ type: Number, required: false }) + @ValidateIf((body) => body.log_created_to) + log_created_to: number; +} + +export class FilterActiveDataSchedulingDto { + // @ApiProperty({ type: 'string', required: true }) + // @ValidateIf((body) => body.schedule_date_from) + // date: Date; +} diff --git a/src/modules/configuration/data-scheduling/infrastructure/guards/setup-scheduling.guard.ts b/src/modules/configuration/data-scheduling/infrastructure/guards/setup-scheduling.guard.ts new file mode 100644 index 0000000..4993947 --- /dev/null +++ b/src/modules/configuration/data-scheduling/infrastructure/guards/setup-scheduling.guard.ts @@ -0,0 +1,40 @@ +import { + CanActivate, + ExecutionContext, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { JwtService } from '@nestjs/jwt'; + +@Injectable() +export class SetupSchedulingGuard implements CanActivate { + constructor(private readonly jwtService: JwtService) {} + + async canActivate(context: ExecutionContext): Promise { + const request = context.switchToHttp().getRequest(); + const jwtAuth = request.headers['authorization']; + const setupAuth = request.headers['x-setup-authorization']; + + if (setupAuth) { + try { + const setupKey = process.env.SETUP_SCHEDULING_KEY; + if (setupAuth === setupKey) return true; + else new UnprocessableEntityException('Setup Authorization Not Found.'); + } catch (err) { + throw new UnprocessableEntityException('Invalid authentication.'); + } + } else if (jwtAuth && jwtAuth.startsWith('Bearer ')) { + const token = jwtAuth.split(' ')[1]; + try { + const payload = await this.jwtService.verifyAsync(token); + if (payload) return true; + else new UnprocessableEntityException('Setup Authorization Not Found.'); + return true; + } catch (err) { + throw new UnprocessableEntityException('Invalid JWT token'); + } + } + + throw new UnprocessableEntityException('Invalid authentication'); + } +} diff --git a/src/modules/configuration/data-scheduling/infrastructure/helpers/index.ts b/src/modules/configuration/data-scheduling/infrastructure/helpers/index.ts new file mode 100644 index 0000000..abc8f47 --- /dev/null +++ b/src/modules/configuration/data-scheduling/infrastructure/helpers/index.ts @@ -0,0 +1,9 @@ +import { EncryptionHelper } from 'src/modules/transaction/sales-price-formula/domain/helpers/encryption.helper'; + +export function encryptionTotal(total: number): string { + return EncryptionHelper.encrypt(btoa(total.toString())); +} + +export function decryptionTotal(total: string): number { + return Number(atob(EncryptionHelper.decrypt(total))); +} diff --git a/src/modules/transaction/sales-price-formula/data/models/sales-price-formula.model.ts b/src/modules/transaction/sales-price-formula/data/models/sales-price-formula.model.ts index eecbf02..587be30 100644 --- a/src/modules/transaction/sales-price-formula/data/models/sales-price-formula.model.ts +++ b/src/modules/transaction/sales-price-formula/data/models/sales-price-formula.model.ts @@ -6,6 +6,7 @@ import { import { Column, Entity } from 'typeorm'; import { BaseModel } from 'src/core/modules/data/model/base.model'; import { FormulaType } from '../../constants'; +import { EncryptionHelper } from '../../domain/helpers/encryption.helper'; @Entity(TABLE_NAME.PRICE_FORMULA) export class SalesPriceFormulaModel @@ -42,4 +43,13 @@ export class TransactionSettingModel { @Column('numeric', { default: 100 }) value: number; + + @Column('varchar', { nullable: true }) + key: string; + + //TODO: add logic to get value from key + percentValue(): number { + const value = EncryptionHelper.decrypt(this.key); + return Number(value) ?? 100; + } } diff --git a/src/modules/transaction/sales-price-formula/data/services/sales-price-formula-data.service.ts b/src/modules/transaction/sales-price-formula/data/services/sales-price-formula-data.service.ts index 66f0fa9..a4f9c3f 100644 --- a/src/modules/transaction/sales-price-formula/data/services/sales-price-formula-data.service.ts +++ b/src/modules/transaction/sales-price-formula/data/services/sales-price-formula-data.service.ts @@ -13,7 +13,10 @@ import { SalesPriceFormulaModel, TransactionSettingModel, } from '../models/sales-price-formula.model'; -import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { + CONNECTION_NAME, + STATUS, +} from 'src/core/strings/constants/base.constants'; import { Repository } from 'typeorm'; import { FormulaType } from '../../constants'; import { TaxModel } from 'src/modules/transaction/tax/data/models/tax.model'; @@ -52,7 +55,7 @@ export class SalesPriceFormulaDataService extends BaseDataService :timestamp', { timestamp: todayTimestamp, }) + .andWhere('transaction.status = :status', { status: STATUS.SETTLED }) .andWhere('transaction.type = :type', { type: TransactionType.COUNTER }) .getRawOne() .then((result) => result.sum || 0), @@ -158,4 +162,15 @@ export class TransactionSettingDataService extends BaseDataService +{ + constructor( + private transactionSettingService: TransactionSettingDataService, // private orchestrator: SalesPriceFormulaDataOrchestrator, + ) {} + + async handle(event: ChangeDocEvent) { + const data = event.data.data; + const database = event.data.database; + + if (database !== 'api_configuration') return; + const value = data.value; + + const currentTransactionSetting = + await this.transactionSettingService.getTransactionSetting(); + + if (value === currentTransactionSetting.value) return; + const key = EncryptionHelper.encrypt(`${value}`); + const payload = { + id: currentTransactionSetting.id, + value: 0, + key, + }; + + await this.transactionSettingService.updateTransactionSetting(payload); + } +} diff --git a/src/modules/transaction/sales-price-formula/infrastructure/dto/sales-price-formula.dto.ts b/src/modules/transaction/sales-price-formula/infrastructure/dto/sales-price-formula.dto.ts index 398f02e..56cc1a4 100644 --- a/src/modules/transaction/sales-price-formula/infrastructure/dto/sales-price-formula.dto.ts +++ b/src/modules/transaction/sales-price-formula/infrastructure/dto/sales-price-formula.dto.ts @@ -46,6 +46,12 @@ export class TransactionSettingDto implements TransactionSetting { required: true, }) value: number; + + @ApiProperty({ + type: String, + required: false, + }) + key: string; } export class SalesPriceFormulaDto diff --git a/src/modules/transaction/sales-price-formula/sales-price-formula.module.ts b/src/modules/transaction/sales-price-formula/sales-price-formula.module.ts index 2b3717f..8183161 100644 --- a/src/modules/transaction/sales-price-formula/sales-price-formula.module.ts +++ b/src/modules/transaction/sales-price-formula/sales-price-formula.module.ts @@ -24,6 +24,7 @@ import { ItemModel } from 'src/modules/item-related/item/data/models/item.model' import { UpdateTransactionSettingManager } from './domain/usecases/managers/update-transaction-setting.manager'; import { TransactionModel } from '../transaction/data/models/transaction.model'; import { CouchModule } from 'src/modules/configuration/couch/couch.module'; +import { TransactionSettingHandler } from './domain/usecases/handlers/transaction-setting.handler'; @Global() @Module({ @@ -58,6 +59,7 @@ import { CouchModule } from 'src/modules/configuration/couch/couch.module'; SalesPriceFormulaDataOrchestrator, SalesPriceFormulaReadOrchestrator, + TransactionSettingHandler, ], exports: [SalesPriceFormulaDataService, SalesPriceFormulaReadService], }) diff --git a/src/modules/transaction/transaction/domain/usecases/handlers/pos-transaction.handler.ts b/src/modules/transaction/transaction/domain/usecases/handlers/pos-transaction.handler.ts index 41e5128..ec52e38 100644 --- a/src/modules/transaction/transaction/domain/usecases/handlers/pos-transaction.handler.ts +++ b/src/modules/transaction/transaction/domain/usecases/handlers/pos-transaction.handler.ts @@ -19,6 +19,7 @@ import { TransactionCreateQueueEvent, } from '../../entities/event/transaction-change-status.event'; import { PriceCalculator } from '../calculator/price.calculator'; +import { Logger } from '@nestjs/common'; @EventsHandler(ChangeDocEvent) export class PosTransactionHandler implements IEventHandler { @@ -44,6 +45,7 @@ export class PosTransactionHandler implements IEventHandler { // jika bukan database transaksi, return langsung if (database != 'transaction') return; + Logger.log(`receipt data with code ${data?.code}`); const sales_formula = await this.formulaService.getOneByOptions({ where: { diff --git a/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/handlers/update-user-privilege-configuration.helper.ts b/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/handlers/update-user-privilege-configuration.helper.ts index 509b891..2efc0a4 100644 --- a/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/handlers/update-user-privilege-configuration.helper.ts +++ b/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/handlers/update-user-privilege-configuration.helper.ts @@ -1,4 +1,8 @@ -import { EventBus, EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { + EventBus, + // EventsHandler, + IEventHandler, +} from '@nestjs/cqrs'; import { UserPrivilegeConfigUpdatedEvent } from '../../../entities/event/user-privilege-configuration-updated.event'; import { UserPrivilegeConfigurationHelper } from '../helpers/generate-user-privilege-configuration.helper'; import { UserPrivilegeDataService } from 'src/modules/user-related/user-privilege/data/service/user-privilege-data.service'; @@ -8,7 +12,9 @@ import { UserPrivilegeUpdatedEvent } from '../../../entities/event/user-privileg import { OPERATION } from 'src/core/strings/constants/base.constants'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; -@EventsHandler(UserPrivilegeConfigUpdatedEvent) +// FIXME => FIX HANDLER FOR ADD NEW MODULE PRIVILEGE CONFIGURATIONS + +// @EventsHandler(UserPrivilegeConfigUpdatedEvent) export class UserPrivilegeConfigUpdateHandler implements IEventHandler { diff --git a/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/managers/update-user-privilege-configuration.manager.ts b/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/managers/update-user-privilege-configuration.manager.ts index 5a61040..aea46b3 100644 --- a/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/managers/update-user-privilege-configuration.manager.ts +++ b/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/managers/update-user-privilege-configuration.manager.ts @@ -6,6 +6,7 @@ import { } from 'src/core/strings/constants/interface.constants'; import { BaseCustomManager } from 'src/core/modules/domain/usecase/managers/base-custom.manager'; import { UserPrivilegeConfigurationModel } from 'src/modules/user-related/user-privilege/data/models/user-privilege-configuration.model'; +import { UserPrivilegeConfigUpdatedEvent } from '../../../entities/event/user-privilege-configuration-updated.event'; @Injectable() export class UpdateUserPrivilegeConfigurationManager extends BaseCustomManager { @@ -54,10 +55,10 @@ export class UpdateUserPrivilegeConfigurationManager extends BaseCustomManager