diff --git a/.gitignore b/.gitignore index fac794b..5b81307 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,7 @@ docker-compose.yml !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json -!.vscode/extensions.json \ No newline at end of file +!.vscode/extensions.json + +# IGNORE UPLOAD FOLDER +/uploads \ No newline at end of file diff --git a/env/env.development b/env/env.development index c2c1623..db61ef4 100644 --- a/env/env.development +++ b/env/env.development @@ -6,6 +6,8 @@ JWT_REFRESH_EXPIRES="7d" ENC_KEY="921c83f3b90c92dca4ba9b947f99b4c9" IV="a671a96159e97a4f" +COUCHDB_CONFIG="http://root:password@172.10.10.2:5970" + DEFAULT_DB_HOST="postgres" DEFAULT_DB_PORT="5432" DEFAULT_DB_USER="root" @@ -15,3 +17,20 @@ DEFAULT_DB_NAME="pos" ELASTIC_APM_ACTIVATE=true ELASTIC_APM_SERVICE_NAME="Skyworld POS" ELASTIC_APM_SERVER_URL="http://172.10.10.10:8200" + +CRON_MIDNIGHT="55 11 * * *" +CRON_EVERY_MINUTE="55 11 * * *" +CRON_EVERY_HOUR="0 * * * *" + +EMAIL_HOST="sandbox.smtp.mailtrap.io" +EMAIL_POST=465 +EMAIL_USER="developer@eigen.co.id" +EMAIL_TOKEN="bitqkbkzjzfywxqx" + +MIDTRANS_URL=https://app.sandbox.midtrans.com +MIDTRANS_PRODUCTION=false +MIDTRANS_SERVER_KEY= +MIDTRANS_CLIENT_KEY= + +EXPORT_LIMIT_PARTITION=200 +ASSETS="https://asset.sky.eigen.co.id/" \ No newline at end of file diff --git a/env/env.production b/env/env.production new file mode 100644 index 0000000..b389dbc --- /dev/null +++ b/env/env.production @@ -0,0 +1,36 @@ +PORT="3346" + +JWT_SECRET="ftyYM4t4kjuj/0ixvIrS18gpdvBJw42NnW71GrFrEhcn0alQkkH7TQIHU5MFFJ1e" +JWT_EXPIRES="24h" +JWT_REFRESH_EXPIRES="7d" +ENC_KEY="921c83f3b90c92dca4ba9b947f99b4c9" +IV="a671a96159e97a4f" + +COUCHDB_CONFIG="http://root:password@172.10.10.2:5970" + +DEFAULT_DB_HOST="postgres" +DEFAULT_DB_PORT="5432" +DEFAULT_DB_USER="root" +DEFAULT_DB_PASS="password" +DEFAULT_DB_NAME="pos" + +ELASTIC_APM_ACTIVATE=true +ELASTIC_APM_SERVICE_NAME="Skyworld POS" +ELASTIC_APM_SERVER_URL="http://172.10.10.10:8200" + +CRON_MIDNIGHT="55 11 * * *" +CRON_EVERY_MINUTE="55 11 * * *" +CRON_EVERY_HOUR="0 * * * *" + +EMAIL_HOST="sandbox.smtp.mailtrap.io" +EMAIL_POST=465 +EMAIL_USER= +EMAIL_TOKEN= + +MIDTRANS_URL=https://app.midtrans.com +MIDTRANS_PRODUCTION=true +MIDTRANS_SERVER_KEY= +MIDTRANS_CLIENT_KEY= + +EXPORT_LIMIT_PARTITION=200 +ASSETS="https://asset.sky.eigen.co.id/" \ No newline at end of file diff --git a/formula-readme.md b/formula-readme.md new file mode 100644 index 0000000..6e409f1 --- /dev/null +++ b/formula-readme.md @@ -0,0 +1,42 @@ +## Formula Calculation + +### Instalation +``` +yarn add mathjs algebra.js +``` + + +### Example +```ts +import * as math from 'mathjs' +import { Equation, parse } from 'algebra.js' + +const formula = 'dpp - (dpp*ppn) - (dpp*retribusi) - (dpp*service) - (dpp*ppn3)' +const total = '300000' + +const variable = { + ppn: 11, + retribusi: 5000, + service: 5, + ppn3: 5000 +} + +try { + + const x1 = math.simplify(formula, variable).toString() + console.log('Formula ', x1) + const dppFormula = parse(x1) + const totalFormula = parse(total) + const equation = new Equation(totalFormula, dppFormula) + + console.log(equation.toString()) + const result = equation.solveFor('dpp').toString() + console.log(result) + + const value = math.evaluate(result) + console.log(value) + +} catch (e) { + console.log(e) +} +``` \ No newline at end of file diff --git a/package.json b/package.json index 6aaad51..eeee924 100644 --- a/package.json +++ b/package.json @@ -34,15 +34,25 @@ "@nestjs/cqrs": "^10.2.7", "@nestjs/jwt": "^10.2.0", "@nestjs/platform-express": "^10.0.0", + "@nestjs/schedule": "^4.1.0", "@nestjs/swagger": "^7.3.1", "@nestjs/typeorm": "^10.0.2", + "@types/multer": "^1.4.11", + "algebra.js": "^0.2.6", "bcrypt": "^5.1.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.1", "dotenv": "^16.4.5", "elastic-apm-node": "^4.5.4", + "exceljs": "^4.4.0", + "fs-extra": "^11.2.0", "googleapis": "^140.0.0", + "handlebars": "^4.7.8", + "mathjs": "^13.0.2", + "midtrans-client": "^1.3.1", + "moment": "^2.30.1", "nano": "^10.1.3", + "nodemailer": "^6.9.14", "pg": "^8.11.5", "plop": "^4.0.1", "reflect-metadata": "^0.2.0", @@ -90,4 +100,4 @@ "coverageDirectory": "../coverage", "testEnvironment": "node" } -} \ No newline at end of file +} diff --git a/plopfile.js b/plopfile.js index 3fc6cf8..c458132 100644 --- a/plopfile.js +++ b/plopfile.js @@ -29,7 +29,7 @@ module.exports = function (plop) { name: 'location', message: 'Location: ', choices: function () { - return ['item related', 'user related', 'season related', 'transaction']; + return ['item related', 'user related', 'season related', 'transaction', 'web information']; }, }, ], diff --git a/src/app.module.ts b/src/app.module.ts index f430b37..08b50d9 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -42,6 +42,33 @@ import { SeasonPeriodModel } from './modules/season-related/season-period/data/m import { ItemRateModule } from './modules/item-related/item-rate/item-rate.module'; import { ItemRateModel } from './modules/item-related/item-rate/data/models/item-rate.model'; import { GoogleCalendarModule } from './modules/configuration/google-calendar/google-calendar.module'; +import { TransactionModule } from './modules/transaction/transaction/transaction.module'; +import { TransactionModel } from './modules/transaction/transaction/data/models/transaction.model'; +import { TransactionItemModel } from './modules/transaction/transaction/data/models/transaction-item.model'; +import { TransactionTaxModel } from './modules/transaction/transaction/data/models/transaction-tax.model'; +import { ReconciliationModule } from './modules/transaction/reconciliation/reconciliation.module'; +import { ReportModule } from './modules/reports/report/report.module'; +import { ReportBookmarkModule } from './modules/reports/report-bookmark/report-bookmark.module'; +import { ReportExportModule } from './modules/reports/report-export/report-export.module'; +import { ReportBookmarkModel } from './modules/reports/shared/models/report-bookmark.model'; +import { ExportReportHistoryModel } from './modules/reports/shared/models/export-report-history.model'; +import { CronModule } from './modules/configuration/cron/cron.module'; +import { MidtransModule } from './modules/configuration/midtrans/midtrans.module'; +import { RefundModule } from './modules/transaction/refund/refund.module'; +import { RefundModel } from './modules/transaction/refund/data/models/refund.model'; +import { RefundItemModel } from './modules/transaction/refund/data/models/refund-item.model'; +import { GateModule } from './modules/web-information/gate/gate.module'; +import { GateModel } from './modules/web-information/gate/data/models/gate.model'; +import { TermConditionModule } from './modules/web-information/term-condition/term-condition.module'; +import { TermConditionModel } from './modules/web-information/term-condition/data/models/term-condition.model'; +import { FaqModel } from './modules/web-information/faq/data/models/faq.model'; +import { FaqModule } from './modules/web-information/faq/faq.module'; +import { UploadModule } from './modules/configuration/upload/upload.module'; +import { NewsModule } from './modules/web-information/news/news.module'; +import { NewsModel } from './modules/web-information/news/data/models/news.model'; +import { BannerModule } from './modules/web-information/banner/banner.module'; +import { BannerModel } from './modules/web-information/banner/data/models/banner.model'; +import { MailModule } from './modules/configuration/mail/mail.module'; @Module({ imports: [ @@ -59,19 +86,33 @@ import { GoogleCalendarModule } from './modules/configuration/google-calendar/go database: process.env.DEFAULT_DB_NAME, entities: [ ...UserPrivilegeModels, + BannerModel, ErrorLogModel, + FaqModel, + GateModel, ItemModel, ItemCategoryModel, ItemRateModel, LogModel, + NewsModel, PaymentMethodModel, + RefundModel, + RefundItemModel, SalesPriceFormulaModel, SeasonPeriodModel, SeasonTypeModel, TaxModel, + TermConditionModel, + TransactionModel, + TransactionItemModel, + TransactionTaxModel, UserModel, VipCategoryModel, VipCodeModel, + + // report + ReportBookmarkModel, + ExportReportHistoryModel, ], synchronize: false, }), @@ -79,9 +120,13 @@ import { GoogleCalendarModule } from './modules/configuration/google-calendar/go ConstantModule, CqrsModule, CouchModule, + CronModule, GoogleCalendarModule, LogModule, + MailModule, + MidtransModule, SessionModule, + UploadModule, // user TenantModule, @@ -96,14 +141,29 @@ import { GoogleCalendarModule } from './modules/configuration/google-calendar/go // transaction PaymentMethodModule, ProfitShareFormulaModule, + ReconciliationModule, + RefundModule, SalesPriceFormulaModule, TaxModule, + TransactionModule, VipCategoryModule, VipCodeModule, // session SeasonTypeModule, SeasonPeriodModule, + + // web information + BannerModule, + FaqModule, + GateModule, + NewsModule, + TermConditionModule, + + // report + ReportModule, + ReportBookmarkModule, + ReportExportModule, ], controllers: [], providers: [ diff --git a/src/core/guards/domain/services/privilege.service.ts b/src/core/guards/domain/services/privilege.service.ts index 27f6809..6cb2917 100644 --- a/src/core/guards/domain/services/privilege.service.ts +++ b/src/core/guards/domain/services/privilege.service.ts @@ -61,7 +61,7 @@ export class PrivilegeService { } async privilegeConfiguration(): Promise { - const { module, menu, sub_menu, section } = this.moduleKey(); + const { module, menu } = this.moduleKey(); return await this.repository.findOne({ select: ['id', 'view', 'create', 'edit', 'delete', 'cancel', 'confirm'], where: { diff --git a/src/core/helpers/path/move-file-path.helper.ts b/src/core/helpers/path/move-file-path.helper.ts new file mode 100644 index 0000000..4fe3c41 --- /dev/null +++ b/src/core/helpers/path/move-file-path.helper.ts @@ -0,0 +1,28 @@ +import * as fs from 'fs-extra'; +import * as path from 'path'; + +export async function MoveFilePathHelper(data) { + const imagePath = data['qr_image'] ?? data['image_url']; + const sourcePath = path.join(__dirname, '../../../../uploads/', imagePath); + const movePath = + 'data/' + + imagePath + .split('/') + .filter((item) => !['uploads', 'tmp'].includes(item)) + .join('/'); + const destinationPath = path.join( + __dirname, + '../../../../uploads/', + movePath, + ); + + try { + await fs.move(sourcePath, destinationPath); + + Object.assign(data, { + image_url: movePath, + }); + } catch (error) { + console.log(`Failed! Error move file data`); + } +} diff --git a/src/core/helpers/path/upload-store-path.helper.ts b/src/core/helpers/path/upload-store-path.helper.ts new file mode 100644 index 0000000..2fbb495 --- /dev/null +++ b/src/core/helpers/path/upload-store-path.helper.ts @@ -0,0 +1,44 @@ +import { extname } from 'path'; +import { v4 as uuidv4 } from 'uuid'; +import { HttpException, HttpStatus } from '@nestjs/common'; +import * as fs from 'fs'; +import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface'; +import { diskStorage } from 'multer'; + +const MB = 1024 * 1024; + +const fileFilter = (req, file, callback) => { + if (file.mimetype.match(/\/(jpg|jpeg|png)$/)) { + callback(null, true); + } else { + callback( + new HttpException( + `Unsupported file type ${extname(file.originalname)}`, + HttpStatus.BAD_REQUEST, + ), + false, + ); + } +}; + +const editFileName = (req, file, callback) => { + const fileExtName = extname(file.originalname); + const randomName = uuidv4(); + callback(null, `${randomName}${fileExtName}`); +}; + +const destinationPath = (req, file, cb) => { + let modulePath = req.body.module; + if (req.body.sub_module) modulePath = `${modulePath}/${req.body.sub_module}`; + + fs.mkdirSync(`./uploads/tmp/${modulePath}`, { recursive: true }); + cb(null, `./uploads/tmp/${modulePath}`); +}; + +export const StoreFileConfig: MulterOptions = { + storage: diskStorage({ + destination: destinationPath, + filename: editFileName, + }), + fileFilter: fileFilter, +}; diff --git a/src/core/helpers/query/default-filter.helper.ts b/src/core/helpers/query/default-filter.helper.ts index 9189025..10f1f81 100644 --- a/src/core/helpers/query/default-filter.helper.ts +++ b/src/core/helpers/query/default-filter.helper.ts @@ -10,39 +10,14 @@ export function setQueryFilterDefault( baseFilter: BaseFilterEntity, tableName: TABLE_NAME, ): SelectQueryBuilder { - // filter berdasarkan statuses - if (!!baseFilter.statuses) { - queryBuilder.andWhere( - new Brackets((qb) => { - baseFilter.statuses.map((status) => { - // trim search - const statusData = status.includes("'") - ? status.trim().replace(/'/g, "''").replace(/\s+/g, ' ') - : status.trim().replace(/\s+/g, ' '); - - // jika searching status terdapat dalam enum, maka dia mencari specific data - // ? karena jika tidak, ketika dia search "active" maka "inactive" juga ikut - if (STATUS[statusData.toUpperCase()]) - qb.orWhere(`${tableName}.status = :statusData`, { - statusData: statusData, - }); - else - qb['orWhere']( - `${tableName}.status::text ILIKE '%${[statusData]}%'`, - ); - }); - }), - ); - } - // filter berdasarkan id pembuat if (!!baseFilter.created_ids) new WhereInQueryHelper( queryBuilder, tableName, - 'created_id', + 'creator_id', baseFilter.created_ids, - 'created_ids', + 'creator_ids', ).getQuery(); // filter berdasarkan tanggal terakhir dibuat diff --git a/src/core/helpers/validation/validate-relation.helper.ts b/src/core/helpers/validation/validate-relation.helper.ts index 6370cf7..2468595 100644 --- a/src/core/helpers/validation/validate-relation.helper.ts +++ b/src/core/helpers/validation/validate-relation.helper.ts @@ -63,7 +63,7 @@ export class ValidateRelationHelper { ) ) throw new UnprocessableEntityException(message); - } else if (data[`total_${relation.relation} `]) + } else if (data[`total_${relation.relation}`] > 0) throw new UnprocessableEntityException(message); } } diff --git a/src/core/modules/data/service/base-data.service.ts b/src/core/modules/data/service/base-data.service.ts index f32e272..e60c841 100644 --- a/src/core/modules/data/service/base-data.service.ts +++ b/src/core/modules/data/service/base-data.service.ts @@ -21,6 +21,15 @@ export abstract class BaseDataService { return await queryRunner.manager.save(newEntity); } + async createMany( + queryRunner: QueryRunner, + entityTarget: EntityTarget, + entity: Entity[], + ): Promise { + const newEntity = queryRunner.manager.create(entityTarget, entity); + return await queryRunner.manager.save(newEntity); + } + async createBatch( queryRunner: QueryRunner, entityTarget: EntityTarget, @@ -68,4 +77,8 @@ export abstract class BaseDataService { async getOneByOptions(findOneOptions): Promise { return await this.repository.findOne(findOneOptions); } + + async getManyByOptions(findOneOptions): Promise { + return await this.repository.find(findOneOptions); + } } diff --git a/src/core/modules/data/service/base-read.service.ts b/src/core/modules/data/service/base-read.service.ts index 6136c01..1b646cc 100644 --- a/src/core/modules/data/service/base-read.service.ts +++ b/src/core/modules/data/service/base-read.service.ts @@ -13,9 +13,11 @@ export abstract class BaseReadService { queryBuilder: SelectQueryBuilder, params: BaseFilterEntity, ): Promise> { + const limit = params.limit ?? 10; + const page = params.page ?? 1; const [data, total] = await queryBuilder - .take(+params.limit) - .skip(+params.limit * +params.page - +params.limit) + .take(+limit) + .skip(+limit * +page - +limit) .getManyAndCount(); return { diff --git a/src/core/modules/domain/usecase/managers/base-batch-delete.manager.ts b/src/core/modules/domain/usecase/managers/base-batch-delete.manager.ts index 374adcb..292c45d 100644 --- a/src/core/modules/domain/usecase/managers/base-batch-delete.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-batch-delete.manager.ts @@ -25,7 +25,7 @@ export abstract class BaseBatchDeleteManager extends BaseManager { async process(): Promise { let totalFailed = 0; let totalSuccess = 0; - let messages = []; + const messages = []; for (const id of this.dataIds) { try { diff --git a/src/core/modules/domain/usecase/managers/base-batch-update-status.manager.ts b/src/core/modules/domain/usecase/managers/base-batch-update-status.manager.ts index bb3ee2b..4125276 100644 --- a/src/core/modules/domain/usecase/managers/base-batch-update-status.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-batch-update-status.manager.ts @@ -4,11 +4,14 @@ import { HttpStatus, NotFoundException } from '@nestjs/common'; import { OPERATION, STATUS } from 'src/core/strings/constants/base.constants'; import { ValidateRelationHelper } from 'src/core/helpers/validation/validate-relation.helper'; import { RecordLog } from 'src/modules/configuration/log/domain/entities/log.event'; +import * as _ from 'lodash'; export abstract class BaseBatchUpdateStatusManager extends BaseManager { protected dataIds: string[]; + protected relations: string[] = []; protected result: BatchResult; protected dataStatus: STATUS; + protected oldData: Entity; abstract get entityTarget(): any; setData(ids: string[], status: STATUS): void { @@ -27,7 +30,7 @@ export abstract class BaseBatchUpdateStatusManager extends BaseManager { async process(): Promise { let totalFailed = 0; let totalSuccess = 0; - let messages = []; + const messages = []; for (const id of this.dataIds) { try { @@ -35,6 +38,7 @@ export abstract class BaseBatchUpdateStatusManager extends BaseManager { where: { id: id, }, + relations: this.relations, }); if (!entity) { @@ -44,6 +48,13 @@ export abstract class BaseBatchUpdateStatusManager extends BaseManager { error: 'Entity Not Found', }); } + this.oldData = _.cloneDeep(entity); + Object.assign(entity, { + status: this.dataStatus, + editor_id: this.user.id, + editor_name: this.user.name, + updated_at: new Date().getTime(), + }); await this.validateData(entity); await new ValidateRelationHelper( @@ -57,15 +68,10 @@ export abstract class BaseBatchUpdateStatusManager extends BaseManager { this.queryRunner, this.entityTarget, { id: id }, - { - status: this.dataStatus, - editor_id: this.user.id, - editor_name: this.user.name, - updated_at: new Date().getTime(), - }, + entity, ); - this.publishEvents(entity, result); + this.publishEvents(this.oldData, result); totalSuccess = totalSuccess + 1; } catch (error) { @@ -101,7 +107,7 @@ export abstract class BaseBatchUpdateStatusManager extends BaseManager { if (!this.eventTopics.length) return; for (const topic of this.eventTopics) { let data; - if (!topic.relations) { + if (topic.relations?.length) { data = await this.dataService.getOneByOptions({ where: { id: dataNew.id, diff --git a/src/core/modules/domain/usecase/managers/base-change-position.manager.ts b/src/core/modules/domain/usecase/managers/base-change-position.manager.ts new file mode 100644 index 0000000..5849753 --- /dev/null +++ b/src/core/modules/domain/usecase/managers/base-change-position.manager.ts @@ -0,0 +1,152 @@ +import { BaseManager } from '../base.manager'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { HttpStatus, UnprocessableEntityException } from '@nestjs/common'; +import { SelectQueryBuilder } from 'typeorm'; + +export abstract class BaseChangePosition extends BaseManager { + protected result: Entity; + protected duplicateColumn: string[]; + protected startData: Entity; + protected endData: Entity; + protected columnSort: string; + + protected firstDataId: number; + protected lastSort: number; + protected sortTo: number; + + abstract get entityTarget(): any; + + setData(entity: Entity, columnSort: string): void { + this.data = entity; + this.columnSort = columnSort; + } + + async beforeProcess(): Promise { + if (!this.data?.end || this.data.start == this.data?.end) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: 'Please drag to another position', + error: 'Unprocessable Entity', + }); + } + + this.startData = await this.dataService.getOneByOptions({ + where: { + id: this.data.start, + }, + }); + + if (!this.startData) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Entity with id : ${this.data.start} not found`, + error: 'Unprocessable Entity', + }); + } + + this.endData = await this.dataService.getOneByOptions({ + where: { + id: this.data.end, + }, + }); + + if (!this.endData) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Entity with id : ${this.data.end} not found`, + error: 'Unprocessable Entity', + }); + } + + if (this.endData[this.columnSort] > this.startData[this.columnSort]) { + // drag from up + this.firstDataId = this.startData[this.columnSort]; + this.lastSort = this.endData[this.columnSort]; + this.sortTo = this.lastSort; + } else if ( + this.endData[this.columnSort] < this.startData[this.columnSort] + ) { + // drag from bottom + this.firstDataId = this.endData[this.columnSort]; + this.lastSort = this.startData[this.columnSort]; + this.sortTo = this.firstDataId; + } + } + + async prepareData(): Promise { + Object.assign(this.data, { + creator_id: this.user.id, + creator_name: this.user.name, + created_at: new Date().getTime(), + updated_at: new Date().getTime(), + }); + } + + async validateProcess(): Promise { + return; + } + + async process(): Promise { + let dataArrange: Entity[]; + + const queryBuilder = this.dataService + .getRepository() + .createQueryBuilder(this.tableName) + .where(`${this.tableName}.${this.columnSort} between :data1 and :data2`, { + data1: this.firstDataId, + data2: this.lastSort, + }); + + const datas = await queryBuilder + .orderBy(`${this.tableName}.${this.columnSort}`, 'ASC') + .getManyAndCount(); + + if (datas[0].length) { + let dataFirst = datas[0][0][this.columnSort]; + const data = datas[0]; + const length = datas[1]; + + if (this.endData[this.columnSort] > this.startData[this.columnSort]) { + // drag from above + const dataDragged = data[0]; + const arraySlice = data.slice(1, length); + dataArrange = arraySlice.concat([dataDragged]); + } else if ( + this.endData[this.columnSort] < this.startData[this.columnSort] + ) { + // drag from bottom + const dataDragged = data[length - 1]; + const arraySlice = data.slice(0, length - 1); + + dataArrange = [dataDragged].concat(arraySlice); + } + + for (let i = 0; i < length; i++) { + dataArrange[i][this.columnSort] = dataFirst; + dataFirst++; + } + + await this.dataService.createMany( + this.queryRunner, + this.entityTarget, + dataArrange, + ); + } + } + + get validateRelations(): validateRelations[] { + return []; + } + + get eventTopics(): EventTopics[] { + return []; + } + + getResult(): string { + return `Success! Data ${this.startData['name']} successfully moved to ${this.sortTo}`; + } +} diff --git a/src/core/modules/domain/usecase/managers/base-create.manager.ts b/src/core/modules/domain/usecase/managers/base-create.manager.ts index fdfb694..92f0019 100644 --- a/src/core/modules/domain/usecase/managers/base-create.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-create.manager.ts @@ -6,6 +6,7 @@ import { columnUniques, validateRelations, } from 'src/core/strings/constants/interface.constants'; +import { MoveFilePathHelper } from 'src/core/helpers/path/move-file-path.helper'; export abstract class BaseCreateManager extends BaseManager { protected result: Entity; @@ -43,6 +44,15 @@ export abstract class BaseCreateManager extends BaseManager { } async process(): Promise { + const keys = Object.keys(this.data); + if ( + (keys.includes('qr_image') || keys.includes('image_url')) && + (this.data['image_url']?.includes('tmp') || + this.data['qr_image']?.includes('tmp')) + ) { + await MoveFilePathHelper(this.data); + } + this.result = await this.dataService.create( this.queryRunner, this.entityTarget, @@ -75,11 +85,21 @@ export abstract class BaseCreateManager extends BaseManager { if (!this.eventTopics.length) return; for (const topic of this.eventTopics) { + let data; + if (!topic.data) { + data = await this.dataService.getOneByOptions({ + where: { + id: this.result['id'], + }, + relations: topic.relations, + }); + } + this.eventBus.publishAll([ new topic.topic({ - id: this.result['id'], + id: data?.['id'] ?? topic?.data?.['id'], old: null, - data: topic.data, + data: data ?? topic.data, user: this.user, description: '', module: this.tableName, diff --git a/src/core/modules/domain/usecase/managers/base-custom.manager.ts b/src/core/modules/domain/usecase/managers/base-custom.manager.ts index 2fd87d7..58215f2 100644 --- a/src/core/modules/domain/usecase/managers/base-custom.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-custom.manager.ts @@ -5,7 +5,7 @@ export abstract class BaseCustomManager extends BaseManager { protected result: any; abstract get entityTarget(): any; - setData(entity: Entity): void { + setData(entity: any): void { this.data = entity; } diff --git a/src/core/modules/domain/usecase/managers/base-delete.manager.ts b/src/core/modules/domain/usecase/managers/base-delete.manager.ts index 4d0692b..3f6e484 100644 --- a/src/core/modules/domain/usecase/managers/base-delete.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-delete.manager.ts @@ -67,7 +67,7 @@ export abstract class BaseDeleteManager extends BaseManager { this.eventBus.publishAll([ new topic.topic({ id: topic.data['id'], - old: null, + old: this.data, data: topic.data, user: this.user, description: '', diff --git a/src/core/modules/domain/usecase/managers/base-index.manager.ts b/src/core/modules/domain/usecase/managers/base-index.manager.ts index 9c757fa..89c2f54 100644 --- a/src/core/modules/domain/usecase/managers/base-index.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-index.manager.ts @@ -8,6 +8,7 @@ import { } from 'src/core/helpers/query/default-filter.helper'; import { Param } from '../../entities/base-filter.entity'; import { joinRelationHelper } from 'src/core/helpers/query/join-relations.helper'; +import { STATUS } from 'src/core/strings/constants/base.constants'; export abstract class BaseIndexManager extends BaseReadManager { protected result: PaginationResponse; @@ -19,6 +20,7 @@ export abstract class BaseIndexManager extends BaseReadManager { } async process(): Promise { + const specificFilter = this.specificFilter; const { joinRelations, selectRelations, countRelations } = this.relations; if (joinRelations?.length) @@ -40,10 +42,26 @@ export abstract class BaseIndexManager extends BaseReadManager { if (this.selects?.length) this.queryBuilder.select(this.selects); + if (this.filterParam.statuses?.length > 0) { + const data = this.filterParam.statuses.map((status) => { + const statusData = status.includes("'") + ? status.trim().replace(/'/g, "''").replace(/\s+/g, ' ') + : status.trim().replace(/\s+/g, ' '); + + // jika searching status terdapat dalam enum, maka dia mencari specific data + // ? karena jika tidak, ketika dia search "active" maka "inactive" juga ikut + return STATUS[statusData.toUpperCase()] ?? statusData; + }); + specificFilter.push({ + cols: `${this.tableName}.status::text`, + data: data, + }); + } + new SpecificSearchFilter( this.queryBuilder, this.tableName, - this.specificFilter, + specificFilter, ).getFilter(); getOrderBy(this.filterParam, this.queryBuilder, this.tableName); diff --git a/src/core/modules/domain/usecase/managers/base-update-status.manager.ts b/src/core/modules/domain/usecase/managers/base-update-status.manager.ts index f3aa9a2..3e4e0a9 100644 --- a/src/core/modules/domain/usecase/managers/base-update-status.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-update-status.manager.ts @@ -9,6 +9,7 @@ export abstract class BaseUpdateStatusManager extends BaseManager { protected result: Entity; protected oldData: Entity; protected dataStatus: STATUS; + protected relations = []; protected duplicateColumn: string[]; abstract get entityTarget(): any; @@ -22,6 +23,7 @@ export abstract class BaseUpdateStatusManager extends BaseManager { where: { id: this.dataId, }, + relations: this.relations, }); this.oldData = _.cloneDeep(this.data); diff --git a/src/core/modules/domain/usecase/managers/base-update.manager.ts b/src/core/modules/domain/usecase/managers/base-update.manager.ts index 77efaa1..29468e0 100644 --- a/src/core/modules/domain/usecase/managers/base-update.manager.ts +++ b/src/core/modules/domain/usecase/managers/base-update.manager.ts @@ -20,6 +20,18 @@ export abstract class BaseUpdateManager extends BaseManager { } async prepareData(): Promise { + this.oldData = await this.dataService.getOneByOptions({ + where: { id: this.dataId }, + }); + + if (!this.oldData) { + throw new NotFoundException({ + statusCode: HttpStatus.NOT_FOUND, + message: `Failed! Entity with id ${this.dataId} not found`, + error: 'Entity Not Found', + }); + } + Object.assign(this.data, { editor_id: this.user.id, editor_name: this.user.name, @@ -38,18 +50,6 @@ export abstract class BaseUpdateManager extends BaseManager { } async process(): Promise { - this.oldData = await this.dataService.getOneByOptions({ - where: { id: this.dataId }, - }); - - if (!this.oldData) { - throw new NotFoundException({ - statusCode: HttpStatus.NOT_FOUND, - message: `Failed! Entity with id ${this.dataId} not found`, - error: 'Entity Not Found', - }); - } - await new ValidateRelationHelper( this.dataId, this.dataService, diff --git a/src/core/modules/domain/usecase/orchestrators/base-data-transaction.orchestrator.ts b/src/core/modules/domain/usecase/orchestrators/base-data-transaction.orchestrator.ts index a3e844f..efecc88 100644 --- a/src/core/modules/domain/usecase/orchestrators/base-data-transaction.orchestrator.ts +++ b/src/core/modules/domain/usecase/orchestrators/base-data-transaction.orchestrator.ts @@ -4,9 +4,9 @@ import { BaseDataOrchestrator } from './base-data.orchestrator'; export abstract class BaseDataTransactionOrchestrator< Entity, > extends BaseDataOrchestrator { - abstract active(dataId: string): Promise; - abstract confirm(dataId: string): Promise; - abstract inactive(dataId: string): Promise; + abstract active(dataId: string): Promise; + abstract confirm(dataId: string): Promise; + abstract inactive(dataId: string): Promise; abstract batchConfirm(dataIds: string[]): Promise; abstract batchActive(dataIds: string[]): Promise; abstract batchInactive(dataIds: string[]): Promise; diff --git a/src/core/modules/domain/usecase/orchestrators/base-data.orchestrator.ts b/src/core/modules/domain/usecase/orchestrators/base-data.orchestrator.ts index 0d61ece..4d4699d 100644 --- a/src/core/modules/domain/usecase/orchestrators/base-data.orchestrator.ts +++ b/src/core/modules/domain/usecase/orchestrators/base-data.orchestrator.ts @@ -3,6 +3,6 @@ import { BatchResult } from 'src/core/response/domain/ok-response.interface'; export abstract class BaseDataOrchestrator { abstract create(data: Entity): Promise; abstract update(dataId: string, data: Entity): Promise; - abstract delete(dataId: string): Promise; + abstract delete(dataId: string): Promise; abstract batchDelete(dataIds: string[]): Promise; } diff --git a/src/core/modules/infrastructure/dto/base-change-position.dto.ts b/src/core/modules/infrastructure/dto/base-change-position.dto.ts new file mode 100644 index 0000000..0b8a90c --- /dev/null +++ b/src/core/modules/infrastructure/dto/base-change-position.dto.ts @@ -0,0 +1,4 @@ +export class ChangePositionDto { + start: string; + end: string; +} diff --git a/src/core/strings/constants/base.constants.ts b/src/core/strings/constants/base.constants.ts index bab60da..0ce2413 100644 --- a/src/core/strings/constants/base.constants.ts +++ b/src/core/strings/constants/base.constants.ts @@ -6,7 +6,9 @@ export enum STATUS { DRAFT = 'draft', EXPIRED = 'expired', INACTIVE = 'inactive', + PARTIAL_REFUND = 'partial refund', PENDING = 'pending', + PROCESS_REFUND = 'proses refund', REFUNDED = 'refunded', REJECTED = 'rejected', SETTLED = 'settled', @@ -46,3 +48,5 @@ export const BLANK_USER = { role: null, user_privilege_id: null, }; + +export const EMPTY_UUID = '00000000-0000-0000-0000-000000000000'; diff --git a/src/core/strings/constants/interface.constants.ts b/src/core/strings/constants/interface.constants.ts index 4c092eb..c5ed49d 100644 --- a/src/core/strings/constants/interface.constants.ts +++ b/src/core/strings/constants/interface.constants.ts @@ -24,7 +24,7 @@ export interface validateRelations { export interface columnUniques { column: string; - query?: Object; + query?: any; } export interface IEvent { diff --git a/src/core/strings/constants/module.constants.ts b/src/core/strings/constants/module.constants.ts index ccc5889..92913b4 100644 --- a/src/core/strings/constants/module.constants.ts +++ b/src/core/strings/constants/module.constants.ts @@ -1,15 +1,27 @@ export enum MODULE_NAME { + BANNER = 'banners', + FAQ = 'faqs', + GATE = 'gates', ITEM = 'items', ITEM_CATEGORY = 'item-categories', ITEM_RATE = 'item-rates', + NEWS = 'news', PAYMENT_METHOD = 'payment-methods', + RECONCILIATION = 'reconciliations', + REFUND = 'refunds', SEASON_TYPE = 'season-types', SEASON_PERIOD = 'season-periods', TAX = 'taxes', + TERM_CONDITION = 'term_conditions', TENANT = 'tenants', + TRANSACTION = 'transactions', USER = 'users', USER_PRIVILEGE = 'user-privileges', USER_PRIVILEGE_CONFIGURATION = 'user-privilege-configurations', VIP_CATEGORY = 'vip-categories', VIP_CODE = 'vip-codes', + + REPORT = 'report', + REPORT_BOOKMARK = 'report-bookmark', + REPORT_EXPORT = 'report-export', } diff --git a/src/core/strings/constants/privilege.constants.ts b/src/core/strings/constants/privilege.constants.ts index ab4ef93..2018bea 100644 --- a/src/core/strings/constants/privilege.constants.ts +++ b/src/core/strings/constants/privilege.constants.ts @@ -47,7 +47,7 @@ export const PrivilegeAdminConstant = [ index: 4, }, { - menu: 'REKONSILIASI', + menu: 'RECONCILIATION', menu_label: 'Rekonsiliasi', actions: [ PrivilegeAction.VIEW, @@ -126,13 +126,13 @@ export const PrivilegeAdminConstant = [ index: 11, }, { - menu: 'LAPORAN', + menu: 'REPORT', menu_label: 'Laporan', actions: [PrivilegeAction.VIEW], index: 12, }, { - menu: 'DISKON_CODE', + menu: 'DISCOUNT_CODE', menu_label: 'Generate Diskon Kode', actions: [PrivilegeAction.CREATE], index: 13, @@ -170,7 +170,7 @@ export const PrivilegePOSConstant = [ index: 17, }, { - menu: 'POS_DISKON_CODE', + menu: 'POS_DISCOUNT_CODE', menu_label: 'Generate Diskon Kode', actions: [PrivilegeAction.CREATE], index: 18, diff --git a/src/core/strings/constants/table.constants.ts b/src/core/strings/constants/table.constants.ts index 53aa6bb..8c14c7d 100644 --- a/src/core/strings/constants/table.constants.ts +++ b/src/core/strings/constants/table.constants.ts @@ -1,15 +1,25 @@ export enum TABLE_NAME { + BANNER = 'banners', ERROR_LOG = 'log_errors', + FAQ = 'faqs', ITEM = 'items', ITEM_CATEGORY = 'item_categories', ITEM_RATE = 'item_rates', + GATE = 'gates', LOG = 'logs', + NEWS = 'news', PAYMENT_METHOD = 'payment_methods', PRICE_FORMULA = 'price_formulas', + REFUND = 'refunds', + REFUND_ITEM = 'refund_items', SEASON_TYPE = 'season_types', SEASON_PERIOD = 'season_periods', TAX = 'taxes', + TERM_CONDITION = 'term_conditions', TENANT = 'tenants', + TRANSACTION = 'transactions', + TRANSACTION_ITEM = 'transaction_items', + TRANSACTION_TAX = 'transaction_taxes', USER = 'users', USER_PRIVILEGE = 'user_privileges', USER_PRIVILEGE_CONFIGURATION = 'user_privilege_configurations', diff --git a/src/core/templates/controllers/base-read/{{dashCase name}}-read.controller.ts.hbs b/src/core/templates/controllers/base-read/{{dashCase name}}-read.controller.ts.hbs index 50598ec..01b8800 100644 --- a/src/core/templates/controllers/base-read/{{dashCase name}}-read.controller.ts.hbs +++ b/src/core/templates/controllers/base-read/{{dashCase name}}-read.controller.ts.hbs @@ -9,7 +9,7 @@ import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; import { Public } from 'src/core/guards'; @ApiTags(`${MODULE_NAME.{{constantCase name}}.split('-').join(' ')} - read`) -@Controller(`v1/${MODULE_NAME.{{constantCase name}}}`) +@Controller(`v1/${MODULE_NAME.{{constantCase name}} }`) @Public(false) @ApiBearerAuth('JWT') export class {{pascalCase name}}ReadController { diff --git a/src/core/templates/controllers/base-status/{{dashCase name}}-data.controller.ts.hbs b/src/core/templates/controllers/base-status/{{dashCase name}}-data.controller.ts.hbs index 8cd174e..755d5a3 100644 --- a/src/core/templates/controllers/base-status/{{dashCase name}}-data.controller.ts.hbs +++ b/src/core/templates/controllers/base-status/{{dashCase name}}-data.controller.ts.hbs @@ -17,7 +17,7 @@ import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto' import { Public } from 'src/core/guards'; @ApiTags(`${MODULE_NAME.{{constantCase name}}.split('-').join(' ')} - data`) -@Controller(`v1/${MODULE_NAME.{{constantCase name}}}`) +@Controller(`v1/${MODULE_NAME.{{constantCase name}} }`) @Public(false) @ApiBearerAuth('JWT') export class {{pascalCase name}}DataController { @@ -36,7 +36,7 @@ export class {{pascalCase name}}DataController { } @Patch(':id/active') - async active(@Param('id') dataId: string): Promise { + async active(@Param('id') dataId: string): Promise { return await this.orchestrator.active(dataId); } @@ -46,7 +46,7 @@ export class {{pascalCase name}}DataController { } @Patch(':id/confirm') - async confirm(@Param('id') dataId: string): Promise { + async confirm(@Param('id') dataId: string): Promise { return await this.orchestrator.confirm(dataId); } @@ -56,7 +56,7 @@ export class {{pascalCase name}}DataController { } @Patch(':id/inactive') - async inactive(@Param('id') dataId: string): Promise { + async inactive(@Param('id') dataId: string): Promise { return await this.orchestrator.inactive(dataId); } @@ -74,7 +74,7 @@ export class {{pascalCase name}}DataController { } @Delete(':id') - async delete(@Param('id') dataId: string): Promise { + async delete(@Param('id') dataId: string): Promise { return await this.orchestrator.delete(dataId); } } diff --git a/src/core/templates/controllers/base/{{dashCase name}}-data.controller.ts.hbs b/src/core/templates/controllers/base/{{dashCase name}}-data.controller.ts.hbs index c725776..706f9df 100644 --- a/src/core/templates/controllers/base/{{dashCase name}}-data.controller.ts.hbs +++ b/src/core/templates/controllers/base/{{dashCase name}}-data.controller.ts.hbs @@ -16,7 +16,7 @@ import { import { Public } from 'src/core/guards'; @ApiTags(`${MODULE_NAME.{{constantCase name}}.split('-').join(' ')} - data`) - @Controller(`v1/${MODULE_NAME.{{constantCase name}}}`) + @Controller(`v1/${MODULE_NAME.{{constantCase name}} }`) @Public(false) @ApiBearerAuth('JWT') export class {{pascalCase name}}DataController { @@ -43,7 +43,7 @@ import { } @Delete(':id') - async delete(@Param('id') dataId: string): Promise { + async delete(@Param('id') dataId: string): Promise { return await this.orchestrator.delete(dataId); } } diff --git a/src/core/templates/orchestrators/base-status/{{dashCase name}}-data.orchestrator.ts.hbs b/src/core/templates/orchestrators/base-status/{{dashCase name}}-data.orchestrator.ts.hbs index 02e4a49..12f0cc0 100644 --- a/src/core/templates/orchestrators/base-status/{{dashCase name}}-data.orchestrator.ts.hbs +++ b/src/core/templates/orchestrators/base-status/{{dashCase name}}-data.orchestrator.ts.hbs @@ -48,7 +48,7 @@ export class {{pascalCase name}}DataOrchestrator extends Base{{pascalCase orches return this.updateManager.getResult(); } - async delete(dataId): Promise { + async delete(dataId): Promise { this.deleteManager.setData(dataId); this.deleteManager.setService(this.serviceData, TABLE_NAME.{{constantCase name}}); await this.deleteManager.execute(); @@ -65,7 +65,7 @@ export class {{pascalCase name}}DataOrchestrator extends Base{{pascalCase orches return this.batchDeleteManager.getResult(); } - async active(dataId): Promise { + async active(dataId): Promise { this.activeManager.setData(dataId, STATUS.ACTIVE); this.activeManager.setService(this.serviceData, TABLE_NAME.{{constantCase name}}); await this.activeManager.execute(); @@ -82,7 +82,7 @@ export class {{pascalCase name}}DataOrchestrator extends Base{{pascalCase orches return this.batchActiveManager.getResult(); } - async confirm(dataId): Promise { + async confirm(dataId): Promise { this.confirmManager.setData(dataId, STATUS.ACTIVE); this.confirmManager.setService(this.serviceData, TABLE_NAME.{{constantCase name}}); await this.confirmManager.execute(); @@ -99,7 +99,7 @@ export class {{pascalCase name}}DataOrchestrator extends Base{{pascalCase orches return this.batchConfirmManager.getResult(); } - async inactive(dataId): Promise { + async inactive(dataId): Promise { this.inactiveManager.setData(dataId, STATUS.INACTIVE); this.inactiveManager.setService( this.serviceData, diff --git a/src/core/templates/orchestrators/base/{{dashCase name}}-data.orchestrator.ts.hbs b/src/core/templates/orchestrators/base/{{dashCase name}}-data.orchestrator.ts.hbs index c34c40b..89e7008 100644 --- a/src/core/templates/orchestrators/base/{{dashCase name}}-data.orchestrator.ts.hbs +++ b/src/core/templates/orchestrators/base/{{dashCase name}}-data.orchestrator.ts.hbs @@ -36,7 +36,7 @@ export class {{pascalCase name}}DataOrchestrator extends Base{{pascalCase orches return this.updateManager.getResult(); } - async delete(dataId): Promise { + async delete(dataId): Promise { this.deleteManager.setData(dataId); this.deleteManager.setService(this.serviceData, TABLE_NAME.{{constantCase name}}); await this.deleteManager.execute(); diff --git a/src/database/migrations/1719572714752-transaction.ts b/src/database/migrations/1719572714752-transaction.ts new file mode 100644 index 0000000..dd488a7 --- /dev/null +++ b/src/database/migrations/1719572714752-transaction.ts @@ -0,0 +1,55 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class Transaction1719572714752 implements MigrationInterface { + name = 'Transaction1719572714752'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "transaction_items" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "item_id" character varying, "item_name" character varying, "item_type" character varying, "item_price" bigint, "item_tenant_id" character varying, "item_tenant_name" character varying, "item_tenant_share_margin" numeric, "total_price" numeric, "total_hpp" numeric, "total_profit" numeric, "total_share_tenant" numeric, "qty" integer, "transaction_id" uuid, CONSTRAINT "PK_ff5a487ad820dccafd53bebf578" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `CREATE TABLE "transaction_taxes" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "tax_id" character varying, "tax_name" character varying, "taxt_value" character varying, "transaction_id" uuid, CONSTRAINT "PK_208b2abdb10640e1991972fc754" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_type_enum" AS ENUM('counter', 'admin', 'online')`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_customer_type_enum" AS ENUM('group', 'vip')`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_payment_type_enum" AS ENUM('midtrans', 'bank transfer', 'qris', 'counter', 'cash', 'credit card', 'debit', 'e-money')`, + ); + await queryRunner.query( + `CREATE TABLE "transactions" ("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"."transactions_status_enum" NOT NULL DEFAULT 'draft', "type" "public"."transactions_type_enum" NOT NULL DEFAULT 'admin', "invoice_code" character varying, "creator_counter_no" integer, "season_period_id" character varying, "season_period_name" character varying, "season_period_type_id" character varying, "season_period_type_name" character varying, "customer_type" "public"."transactions_customer_type_enum", "customer_category_id" character varying, "customer_category_name" character varying, "customer_name" character varying, "customer_phone" character varying, "customer_email" character varying, "customer_description" character varying, "no_of_group" character varying, "booking_date" date, "settlement_date" date, "invoice_date" date, "discount_code_id" character varying, "discount_code" character varying, "discount_percentage" integer, "discount_value" numeric, "payment_type" "public"."transactions_payment_type_enum" NOT NULL DEFAULT 'bank transfer', "payment_type_method_id" character varying, "payment_type_method_name" character varying, "payment_type_method_qr" character varying, "payment_card_information" character varying, "payment_code_reference" character varying, "payment_date" date, "payment_sub_total" numeric, "payment_discount_total" numeric, "payment_total" numeric, "payment_total_pay" numeric, "payment_change" numeric, "payment_total_share" numeric, "payment_total_tax" numeric, "payment_total_profit" numeric, "profit_share_formula" character varying, "sales_price_formula" character varying, CONSTRAINT "PK_a219afd8dd77ed80f5a862f1db9" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" ADD CONSTRAINT "FK_5926425896b30c0d681fe879af0" FOREIGN KEY ("transaction_id") REFERENCES "transactions"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_taxes" ADD CONSTRAINT "FK_d21db1756c6656efc7c082fbaa6" FOREIGN KEY ("transaction_id") REFERENCES "transactions"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transaction_taxes" DROP CONSTRAINT "FK_d21db1756c6656efc7c082fbaa6"`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" DROP CONSTRAINT "FK_5926425896b30c0d681fe879af0"`, + ); + await queryRunner.query(`DROP TABLE "transactions"`); + await queryRunner.query( + `DROP TYPE "public"."transactions_payment_type_enum"`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_customer_type_enum"`, + ); + await queryRunner.query(`DROP TYPE "public"."transactions_type_enum"`); + await queryRunner.query(`DROP TYPE "public"."transactions_status_enum"`); + await queryRunner.query(`DROP TABLE "transaction_taxes"`); + await queryRunner.query(`DROP TABLE "transaction_items"`); + } +} diff --git a/src/database/migrations/1719925690145-add-reconciliation-to-transaction.ts b/src/database/migrations/1719925690145-add-reconciliation-to-transaction.ts new file mode 100644 index 0000000..7914ef1 --- /dev/null +++ b/src/database/migrations/1719925690145-add-reconciliation-to-transaction.ts @@ -0,0 +1,61 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddReconciliationToTransaction1719925690145 + implements MigrationInterface +{ + name = 'AddReconciliationToTransaction1719925690145'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" ADD "is_recap_transaction" boolean NOT NULL DEFAULT true`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "payment_type_method_number" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "reconciliation_mdr" numeric`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_reconciliation_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "reconciliation_status" "public"."transactions_reconciliation_status_enum" NOT NULL DEFAULT 'draft'`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "reconciliation_confirm_date" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "reconciliation_confirm_by" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "payment_total_net_profit" numeric`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "payment_total_net_profit"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "reconciliation_confirm_by"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "reconciliation_confirm_date"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "reconciliation_status"`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_reconciliation_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "reconciliation_mdr"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "payment_type_method_number"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "is_recap_transaction"`, + ); + } +} diff --git a/src/database/migrations/1719934464407-update-table-transaction.ts b/src/database/migrations/1719934464407-update-table-transaction.ts new file mode 100644 index 0000000..cbf38d9 --- /dev/null +++ b/src/database/migrations/1719934464407-update-table-transaction.ts @@ -0,0 +1,71 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateTableTransaction1719934464407 implements MigrationInterface { + name = 'UpdateTableTransaction1719934464407'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transaction_items" ADD "item_hpp" bigint`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" ADD "item_category_id" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" ADD "item_category_name" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" ADD "item_bundlings" json`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_sending_invoice_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "sending_invoice_status" "public"."transactions_sending_invoice_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "sending_invoice_at" bigint`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_sending_qr_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "sending_qr_status" "public"."transactions_sending_qr_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "sending_qr_at" bigint`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "sending_qr_at"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "sending_qr_status"`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_sending_qr_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "sending_invoice_at"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "sending_invoice_status"`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_sending_invoice_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" DROP COLUMN "item_bundlings"`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" DROP COLUMN "item_category_name"`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" DROP COLUMN "item_category_id"`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" DROP COLUMN "item_hpp"`, + ); + } +} diff --git a/src/database/migrations/1719982860855-add-column-type-report-bookmark.ts b/src/database/migrations/1719982860855-add-column-type-report-bookmark.ts new file mode 100644 index 0000000..30129f7 --- /dev/null +++ b/src/database/migrations/1719982860855-add-column-type-report-bookmark.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddColumnTypeReportBookmark1719982860855 + implements MigrationInterface +{ + name = 'AddColumnTypeReportBookmark1719982860855'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."report_bookmark_type_enum" AS ENUM('TABLE_CONFIG', 'FILTER_TABLE')`, + ); + await queryRunner.query( + `ALTER TABLE "report_bookmark" ADD "type" "public"."report_bookmark_type_enum" NOT NULL DEFAULT 'TABLE_CONFIG'`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "report_bookmark" DROP COLUMN "type"`); + await queryRunner.query(`DROP TYPE "public"."report_bookmark_type_enum"`); + } +} diff --git a/src/database/migrations/1720077765890-update-default-column-transaction.ts b/src/database/migrations/1720077765890-update-default-column-transaction.ts new file mode 100644 index 0000000..bc8fc89 --- /dev/null +++ b/src/database/migrations/1720077765890-update-default-column-transaction.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateDefaultColumnTransaction1720077765890 + implements MigrationInterface +{ + name = 'UpdateDefaultColumnTransaction1720077765890'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "is_recap_transaction" SET DEFAULT false`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "is_recap_transaction" SET DEFAULT true`, + ); + } +} diff --git a/src/database/migrations/1720436852936-update-table-transaction.ts b/src/database/migrations/1720436852936-update-table-transaction.ts new file mode 100644 index 0000000..05e41b1 --- /dev/null +++ b/src/database/migrations/1720436852936-update-table-transaction.ts @@ -0,0 +1,29 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateTableTransaction1720436852936 implements MigrationInterface { + name = 'UpdateTableTransaction1720436852936'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transaction_taxes" ADD "tax_total_value" numeric`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_taxes" DROP COLUMN "taxt_value"`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_taxes" ADD "taxt_value" numeric`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transaction_taxes" DROP COLUMN "taxt_value"`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_taxes" ADD "taxt_value" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_taxes" DROP COLUMN "tax_total_value"`, + ); + } +} diff --git a/src/database/migrations/1720767689625-update-table-transaction.ts b/src/database/migrations/1720767689625-update-table-transaction.ts new file mode 100644 index 0000000..2885e26 --- /dev/null +++ b/src/database/migrations/1720767689625-update-table-transaction.ts @@ -0,0 +1,27 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateTableTransaction1720767689625 implements MigrationInterface { + name = 'UpdateTableTransaction1720767689625'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transaction_items" ADD "qty_remaining" integer`, + ); + await queryRunner.query(`ALTER TABLE "transaction_items" ADD "taxes" json`); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "payment_total_dpp" numeric`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "payment_total_dpp"`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" DROP COLUMN "taxes"`, + ); + await queryRunner.query( + `ALTER TABLE "transaction_items" DROP COLUMN "qty_remaining"`, + ); + } +} diff --git a/src/database/migrations/1720768975877-refund.ts b/src/database/migrations/1720768975877-refund.ts new file mode 100644 index 0000000..5e00472 --- /dev/null +++ b/src/database/migrations/1720768975877-refund.ts @@ -0,0 +1,45 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class Refund1720768975877 implements MigrationInterface { + name = 'Refund1720768975877'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."refunds_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `CREATE TYPE "public"."refunds_type_enum" AS ENUM('pengembalian booking', 'pengembalian wahana')`, + ); + await queryRunner.query( + `CREATE TABLE "refunds" ("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"."refunds_status_enum" NOT NULL DEFAULT 'draft', "type" "public"."refunds_type_enum" NOT NULL DEFAULT 'pengembalian booking', "code" character varying, "request_date" date, "refund_date" date, "refund_total" numeric, "bank_name" character varying, "bank_account_name" character varying, "bank_account_number" character varying, "transaction_id" uuid, CONSTRAINT "REL_8bb3b7579f49990d2e77684acd" UNIQUE ("transaction_id"), CONSTRAINT "PK_5106efb01eeda7e49a78b869738" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `CREATE TABLE "refund_items" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "qty_refund" numeric, "refund_total" numeric, "refund_item_id" uuid, "transaction_item_id" uuid, CONSTRAINT "REL_07b481a163c219f5de8fb1c90b" UNIQUE ("transaction_item_id"), CONSTRAINT "PK_ef892918375a6101948b90f1140" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ADD CONSTRAINT "FK_8bb3b7579f49990d2e77684acd4" FOREIGN KEY ("transaction_id") REFERENCES "transactions"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "refund_items" ADD CONSTRAINT "FK_2a4bd60fb8a9c37f902f4f3da67" FOREIGN KEY ("refund_item_id") REFERENCES "refunds"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + await queryRunner.query( + `ALTER TABLE "refund_items" ADD CONSTRAINT "FK_07b481a163c219f5de8fb1c90b3" FOREIGN KEY ("transaction_item_id") REFERENCES "transaction_items"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "refund_items" DROP CONSTRAINT "FK_07b481a163c219f5de8fb1c90b3"`, + ); + await queryRunner.query( + `ALTER TABLE "refund_items" DROP CONSTRAINT "FK_2a4bd60fb8a9c37f902f4f3da67"`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" DROP CONSTRAINT "FK_8bb3b7579f49990d2e77684acd4"`, + ); + await queryRunner.query(`DROP TABLE "refund_items"`); + await queryRunner.query(`DROP TABLE "refunds"`); + await queryRunner.query(`DROP TYPE "public"."refunds_type_enum"`); + await queryRunner.query(`DROP TYPE "public"."refunds_status_enum"`); + } +} diff --git a/src/database/migrations/1720774145470-update-enum-status.ts b/src/database/migrations/1720774145470-update-enum-status.ts new file mode 100644 index 0000000..3c44eb3 --- /dev/null +++ b/src/database/migrations/1720774145470-update-enum-status.ts @@ -0,0 +1,461 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateEnumStatus1720774145470 implements MigrationInterface { + name = 'UpdateEnumStatus1720774145470'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TYPE "public"."item_categories_status_enum" RENAME TO "item_categories_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."item_categories_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "item_categories" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "item_categories" ALTER COLUMN "status" TYPE "public"."item_categories_status_enum" USING "status"::"text"::"public"."item_categories_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "item_categories" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."item_categories_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."season_types_status_enum" RENAME TO "season_types_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."season_types_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "season_types" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "season_types" ALTER COLUMN "status" TYPE "public"."season_types_status_enum" USING "status"::"text"::"public"."season_types_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "season_types" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."season_types_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."season_periods_status_enum" RENAME TO "season_periods_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."season_periods_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "season_periods" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "season_periods" ALTER COLUMN "status" TYPE "public"."season_periods_status_enum" USING "status"::"text"::"public"."season_periods_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "season_periods" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."season_periods_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."items_status_enum" RENAME TO "items_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."items_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "items" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "items" ALTER COLUMN "status" TYPE "public"."items_status_enum" USING "status"::"text"::"public"."items_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "items" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."items_status_enum_old"`); + await queryRunner.query( + `ALTER TYPE "public"."users_status_enum" RENAME TO "users_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."users_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "users" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "users" ALTER COLUMN "status" TYPE "public"."users_status_enum" USING "status"::"text"::"public"."users_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "users" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."users_status_enum_old"`); + await queryRunner.query( + `ALTER TYPE "public"."user_privileges_status_enum" RENAME TO "user_privileges_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."user_privileges_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "user_privileges" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "user_privileges" ALTER COLUMN "status" TYPE "public"."user_privileges_status_enum" USING "status"::"text"::"public"."user_privileges_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "user_privileges" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."user_privileges_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."refunds_status_enum" RENAME TO "refunds_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."refunds_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ALTER COLUMN "status" TYPE "public"."refunds_status_enum" USING "status"::"text"::"public"."refunds_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."refunds_status_enum_old"`); + await queryRunner.query( + `ALTER TYPE "public"."transactions_status_enum" RENAME TO "transactions_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "status" TYPE "public"."transactions_status_enum" USING "status"::"text"::"public"."transactions_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."transactions_reconciliation_status_enum" RENAME TO "transactions_reconciliation_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_reconciliation_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" TYPE "public"."transactions_reconciliation_status_enum" USING "reconciliation_status"::"text"::"public"."transactions_reconciliation_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_reconciliation_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."transactions_sending_invoice_status_enum" RENAME TO "transactions_sending_invoice_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_sending_invoice_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "sending_invoice_status" TYPE "public"."transactions_sending_invoice_status_enum" USING "sending_invoice_status"::"text"::"public"."transactions_sending_invoice_status_enum"`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_sending_invoice_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."transactions_sending_qr_status_enum" RENAME TO "transactions_sending_qr_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_sending_qr_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "sending_qr_status" TYPE "public"."transactions_sending_qr_status_enum" USING "sending_qr_status"::"text"::"public"."transactions_sending_qr_status_enum"`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_sending_qr_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."vip_categories_status_enum" RENAME TO "vip_categories_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."vip_categories_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "vip_categories" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "vip_categories" ALTER COLUMN "status" TYPE "public"."vip_categories_status_enum" USING "status"::"text"::"public"."vip_categories_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "vip_categories" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."vip_categories_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."taxes_status_enum" RENAME TO "taxes_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."taxes_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "taxes" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "taxes" ALTER COLUMN "status" TYPE "public"."taxes_status_enum" USING "status"::"text"::"public"."taxes_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "taxes" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."taxes_status_enum_old"`); + await queryRunner.query( + `ALTER TYPE "public"."payment_methods_status_enum" RENAME TO "payment_methods_status_enum_old"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."payment_methods_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "payment_methods" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "payment_methods" ALTER COLUMN "status" TYPE "public"."payment_methods_status_enum" USING "status"::"text"::"public"."payment_methods_status_enum"`, + ); + await queryRunner.query( + `ALTER TABLE "payment_methods" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."payment_methods_status_enum_old"`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."payment_methods_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "payment_methods" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "payment_methods" ALTER COLUMN "status" TYPE "public"."payment_methods_status_enum_old" USING "status"::"text"::"public"."payment_methods_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "payment_methods" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."payment_methods_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."payment_methods_status_enum_old" RENAME TO "payment_methods_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."taxes_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "taxes" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "taxes" ALTER COLUMN "status" TYPE "public"."taxes_status_enum_old" USING "status"::"text"::"public"."taxes_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "taxes" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."taxes_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."taxes_status_enum_old" RENAME TO "taxes_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."vip_categories_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "vip_categories" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "vip_categories" ALTER COLUMN "status" TYPE "public"."vip_categories_status_enum_old" USING "status"::"text"::"public"."vip_categories_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "vip_categories" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."vip_categories_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."vip_categories_status_enum_old" RENAME TO "vip_categories_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_sending_qr_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "sending_qr_status" TYPE "public"."transactions_sending_qr_status_enum_old" USING "sending_qr_status"::"text"::"public"."transactions_sending_qr_status_enum_old"`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_sending_qr_status_enum"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."transactions_sending_qr_status_enum_old" RENAME TO "transactions_sending_qr_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_sending_invoice_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "sending_invoice_status" TYPE "public"."transactions_sending_invoice_status_enum_old" USING "sending_invoice_status"::"text"::"public"."transactions_sending_invoice_status_enum_old"`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_sending_invoice_status_enum"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."transactions_sending_invoice_status_enum_old" RENAME TO "transactions_sending_invoice_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_reconciliation_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" TYPE "public"."transactions_reconciliation_status_enum_old" USING "reconciliation_status"::"text"::"public"."transactions_reconciliation_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" SET DEFAULT 'draft'`, + ); + await queryRunner.query( + `DROP TYPE "public"."transactions_reconciliation_status_enum"`, + ); + await queryRunner.query( + `ALTER TYPE "public"."transactions_reconciliation_status_enum_old" RENAME TO "transactions_reconciliation_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."transactions_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "status" TYPE "public"."transactions_status_enum_old" USING "status"::"text"::"public"."transactions_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."transactions_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."transactions_status_enum_old" RENAME TO "transactions_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."refunds_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ALTER COLUMN "status" TYPE "public"."refunds_status_enum_old" USING "status"::"text"::"public"."refunds_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "refunds" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."refunds_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."refunds_status_enum_old" RENAME TO "refunds_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."user_privileges_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "user_privileges" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "user_privileges" ALTER COLUMN "status" TYPE "public"."user_privileges_status_enum_old" USING "status"::"text"::"public"."user_privileges_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "user_privileges" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."user_privileges_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."user_privileges_status_enum_old" RENAME TO "user_privileges_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."users_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "users" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "users" ALTER COLUMN "status" TYPE "public"."users_status_enum_old" USING "status"::"text"::"public"."users_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "users" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."users_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."users_status_enum_old" RENAME TO "users_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."items_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "items" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "items" ALTER COLUMN "status" TYPE "public"."items_status_enum_old" USING "status"::"text"::"public"."items_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "items" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."items_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."items_status_enum_old" RENAME TO "items_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."season_periods_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "season_periods" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "season_periods" ALTER COLUMN "status" TYPE "public"."season_periods_status_enum_old" USING "status"::"text"::"public"."season_periods_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "season_periods" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."season_periods_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."season_periods_status_enum_old" RENAME TO "season_periods_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."season_types_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "season_types" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "season_types" ALTER COLUMN "status" TYPE "public"."season_types_status_enum_old" USING "status"::"text"::"public"."season_types_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "season_types" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."season_types_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."season_types_status_enum_old" RENAME TO "season_types_status_enum"`, + ); + await queryRunner.query( + `CREATE TYPE "public"."item_categories_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `ALTER TABLE "item_categories" ALTER COLUMN "status" DROP DEFAULT`, + ); + await queryRunner.query( + `ALTER TABLE "item_categories" ALTER COLUMN "status" TYPE "public"."item_categories_status_enum_old" USING "status"::"text"::"public"."item_categories_status_enum_old"`, + ); + await queryRunner.query( + `ALTER TABLE "item_categories" ALTER COLUMN "status" SET DEFAULT 'draft'`, + ); + await queryRunner.query(`DROP TYPE "public"."item_categories_status_enum"`); + await queryRunner.query( + `ALTER TYPE "public"."item_categories_status_enum_old" RENAME TO "item_categories_status_enum"`, + ); + } +} diff --git a/src/database/migrations/1721024987609-gate.ts b/src/database/migrations/1721024987609-gate.ts new file mode 100644 index 0000000..d2d906e --- /dev/null +++ b/src/database/migrations/1721024987609-gate.ts @@ -0,0 +1,29 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class Gate1721024987609 implements MigrationInterface { + name = 'Gate1721024987609'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."gates_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"."gates_type_enum" AS ENUM('gate masuk', 'gate keluar')`, + ); + await queryRunner.query( + `CREATE TABLE "gates" ("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"."gates_status_enum" NOT NULL DEFAULT 'draft', "type" "public"."gates_type_enum" NOT NULL DEFAULT 'gate masuk', "code" character varying, "note" text, "item_id" uuid, CONSTRAINT "PK_2dd58a77462dd2c5695ec4a7975" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `ALTER TABLE "gates" ADD CONSTRAINT "FK_29f020cd153bb079722bcbee830" FOREIGN KEY ("item_id") REFERENCES "items"("id") ON DELETE CASCADE ON UPDATE CASCADE`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "gates" DROP CONSTRAINT "FK_29f020cd153bb079722bcbee830"`, + ); + await queryRunner.query(`DROP TABLE "gates"`); + await queryRunner.query(`DROP TYPE "public"."gates_type_enum"`); + await queryRunner.query(`DROP TYPE "public"."gates_status_enum"`); + } +} diff --git a/src/database/migrations/1721029248635-term-condition.ts b/src/database/migrations/1721029248635-term-condition.ts new file mode 100644 index 0000000..2f4ebf4 --- /dev/null +++ b/src/database/migrations/1721029248635-term-condition.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class TermCondition1721029248635 implements MigrationInterface { + name = 'TermCondition1721029248635'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."term_conditions_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `CREATE TABLE "term_conditions" ("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"."term_conditions_status_enum" NOT NULL DEFAULT 'draft', "title" character varying, "description" text, CONSTRAINT "PK_fc92769e487820f24ed68337feb" PRIMARY KEY ("id"))`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "term_conditions"`); + await queryRunner.query(`DROP TYPE "public"."term_conditions_status_enum"`); + } +} diff --git a/src/database/migrations/1721029454627-frequently-ask-question.ts b/src/database/migrations/1721029454627-frequently-ask-question.ts new file mode 100644 index 0000000..9fb3173 --- /dev/null +++ b/src/database/migrations/1721029454627-frequently-ask-question.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class FrequentlyAskQuestion1721029454627 implements MigrationInterface { + name = 'FrequentlyAskQuestion1721029454627'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."faqs_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `CREATE TABLE "faqs" ("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"."faqs_status_enum" NOT NULL DEFAULT 'draft', "title" character varying, "description" text, CONSTRAINT "PK_2ddf4f2c910f8e8fa2663a67bf0" PRIMARY KEY ("id"))`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "faqs"`); + await queryRunner.query(`DROP TYPE "public"."faqs_status_enum"`); + } +} diff --git a/src/database/migrations/1721031712642-update-refund.ts b/src/database/migrations/1721031712642-update-refund.ts new file mode 100644 index 0000000..09c5d4c --- /dev/null +++ b/src/database/migrations/1721031712642-update-refund.ts @@ -0,0 +1,17 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateRefund1721031712642 implements MigrationInterface { + name = 'UpdateRefund1721031712642'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "refunds" ADD "refund_sub_total" numeric`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "refunds" DROP COLUMN "refund_sub_total"`, + ); + } +} diff --git a/src/database/migrations/1721109817371-news.ts b/src/database/migrations/1721109817371-news.ts new file mode 100644 index 0000000..cfabc56 --- /dev/null +++ b/src/database/migrations/1721109817371-news.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class News1721109817371 implements MigrationInterface { + name = 'News1721109817371'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."news_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `CREATE TABLE "news" ("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"."news_status_enum" NOT NULL DEFAULT 'draft', "image_url" character varying, "title" character varying, "teaser" character varying, "description" character varying, CONSTRAINT "PK_39a43dfcb6007180f04aff2357e" PRIMARY KEY ("id"))`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "news"`); + await queryRunner.query(`DROP TYPE "public"."news_status_enum"`); + } +} diff --git a/src/database/migrations/1721111093665-banner.ts b/src/database/migrations/1721111093665-banner.ts new file mode 100644 index 0000000..c983c33 --- /dev/null +++ b/src/database/migrations/1721111093665-banner.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class Banner1721111093665 implements MigrationInterface { + name = 'Banner1721111093665'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."banners_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`, + ); + await queryRunner.query( + `CREATE TABLE "banners" ("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"."banners_status_enum" NOT NULL DEFAULT 'draft', "image_url" character varying, "title" character varying, "link" character varying, CONSTRAINT "PK_e9b186b959296fcb940790d31c3" PRIMARY KEY ("id"))`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "banners"`); + await queryRunner.query(`DROP TYPE "public"."banners_status_enum"`); + } +} diff --git a/src/database/migrations/1721216510569-update-table-transactions.ts b/src/database/migrations/1721216510569-update-table-transactions.ts new file mode 100644 index 0000000..5e6380a --- /dev/null +++ b/src/database/migrations/1721216510569-update-table-transactions.ts @@ -0,0 +1,25 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateTableTransactions1721216510569 + implements MigrationInterface +{ + name = 'UpdateTableTransactions1721216510569'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" ADD "payment_midtrans_token" character varying`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "payment_midtrans_url" character varying`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "payment_midtrans_url"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "payment_midtrans_token"`, + ); + } +} diff --git a/src/database/migrations/1721284172572-update-sort-column.ts b/src/database/migrations/1721284172572-update-sort-column.ts new file mode 100644 index 0000000..cd3464d --- /dev/null +++ b/src/database/migrations/1721284172572-update-sort-column.ts @@ -0,0 +1,21 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateSortColumn1721284172572 implements MigrationInterface { + name = 'UpdateSortColumn1721284172572'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "term_conditions" ADD "sort_order" integer NOT NULL DEFAULT '0'`, + ); + await queryRunner.query( + `ALTER TABLE "faqs" ADD "sort_order" integer NOT NULL DEFAULT '0'`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "faqs" DROP COLUMN "sort_order"`); + await queryRunner.query( + `ALTER TABLE "term_conditions" DROP COLUMN "sort_order"`, + ); + } +} diff --git a/src/database/migrations/1721385120750-update-column-transaction.ts b/src/database/migrations/1721385120750-update-column-transaction.ts new file mode 100644 index 0000000..ebec03d --- /dev/null +++ b/src/database/migrations/1721385120750-update-column-transaction.ts @@ -0,0 +1,25 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateColumnTransaction1721385120750 + implements MigrationInterface +{ + name = 'UpdateColumnTransaction1721385120750'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "discount_percentage"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "discount_percentage" numeric`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "transactions" DROP COLUMN "discount_percentage"`, + ); + await queryRunner.query( + `ALTER TABLE "transactions" ADD "discount_percentage" integer`, + ); + } +} diff --git a/src/database/migrations/1721647955446-update-image-column-item.ts b/src/database/migrations/1721647955446-update-image-column-item.ts new file mode 100644 index 0000000..f337d7e --- /dev/null +++ b/src/database/migrations/1721647955446-update-image-column-item.ts @@ -0,0 +1,17 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class UpdateImageColumnItem1721647955446 implements MigrationInterface { + name = 'UpdateImageColumnItem1721647955446'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "items" RENAME COLUMN "image" TO "image_url"`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "items" RENAME COLUMN "image_url" TO "image"`, + ); + } +} diff --git a/src/database/seeds/1718164659-default-formula.seed.ts b/src/database/seeds/1718164659-default-formula.seed.ts index 461bc7f..5364bcf 100644 --- a/src/database/seeds/1718164659-default-formula.seed.ts +++ b/src/database/seeds/1718164659-default-formula.seed.ts @@ -29,8 +29,6 @@ export class SeedDefaultFormula implements Seeder { .into(SalesPriceFormulaModel) .values([sales_formula, profit_formula]) .execute(); - } catch (error) { - console.log(error, 'er'); - } + } catch (error) {} } } diff --git a/src/modules/configuration/constant/infrastructure/constant.controller.ts b/src/modules/configuration/constant/infrastructure/constant.controller.ts index 32ad74c..a388236 100644 --- a/src/modules/configuration/constant/infrastructure/constant.controller.ts +++ b/src/modules/configuration/constant/infrastructure/constant.controller.ts @@ -5,13 +5,13 @@ import { STATUS } from 'src/core/strings/constants/base.constants'; import { ItemType } from 'src/modules/item-related/item-category/constants'; import { LimitType } from 'src/modules/item-related/item/constants'; import { PaymentMethodType } from 'src/modules/transaction/payment-method/constants'; +import { RefundType } from 'src/modules/transaction/refund/constants'; +import { GateType } from 'src/modules/web-information/gate/constants'; @ApiTags('configuration - constant') @Controller('v1/constant') @Public(true) export class ConstantController { - constructor() {} - @Get('master-data-status') async masterDataStatus(): Promise { return [STATUS.ACTIVE, STATUS.DRAFT, STATUS.INACTIVE]; @@ -31,4 +31,29 @@ export class ConstantController { async paymentMethodType(): Promise { return Object.values(PaymentMethodType); } + + @Get('transaction-user-type') + async userType(): Promise { + return ['group', 'vip']; + } + + @Get('transaction-payment-type') + async transactionPaymentType(): Promise { + return ['midtrans', 'bank transfer', 'qris', 'counter']; + } + + @Get('transaction-type') + async transactionType(): Promise { + return ['counter', 'admin', 'online']; + } + + @Get('refund-type') + async refundType(): Promise { + return Object.values(RefundType); + } + + @Get('gate-type') + async gateType(): Promise { + return Object.values(GateType); + } } diff --git a/src/modules/configuration/couch/constants.ts b/src/modules/configuration/couch/constants.ts index c3045f2..031c3c4 100644 --- a/src/modules/configuration/couch/constants.ts +++ b/src/modules/configuration/couch/constants.ts @@ -1 +1 @@ -export const DatabaseListen = ['transaction']; +export const DatabaseListen = ['transaction', 'vip_code']; diff --git a/src/modules/configuration/couch/couch.module.ts b/src/modules/configuration/couch/couch.module.ts index aa75b30..e3a4b95 100644 --- a/src/modules/configuration/couch/couch.module.ts +++ b/src/modules/configuration/couch/couch.module.ts @@ -15,18 +15,64 @@ import { SeasonPeriodDeletedHandler, SeasonPeriodUpdatedHandler, } from './domain/managers/season-period.handler'; +import { + ItemDeletedHandler, + ItemUpdatedHandler, +} from './domain/managers/item.handler'; +import { + UserDeletedHandler, + UserUpdatedHandler, +} from './domain/managers/user.handler'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'; +import { UserModel } from 'src/modules/user-related/user/data/models/user.model'; +import { UserDataService } from 'src/modules/user-related/user/data/services/user-data.service'; +import { ItemDataService } from 'src/modules/item-related/item/data/services/item-data.service'; +import { + BookingDeletedEvent, + BookingHandler, +} from './domain/managers/booking.handler'; +import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { TransactionTaxModel } from 'src/modules/transaction/transaction/data/models/transaction-tax.model'; +import { TransactionItemModel } from 'src/modules/transaction/transaction/data/models/transaction-item.model'; +import { VipCodeCreatedHandler } from './domain/managers/vip-code.handler'; @Module({ - imports: [ConfigModule.forRoot(), CqrsModule], + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature( + [ + ItemModel, + UserModel, + TransactionModel, + TransactionTaxModel, + TransactionItemModel, + ], + CONNECTION_NAME.DEFAULT, + ), + CqrsModule, + ], controllers: [CouchDataController], providers: [ + BookingHandler, + BookingDeletedEvent, PaymentMethodDeletedHandler, PaymentMethodUpdatedHandler, + VipCodeCreatedHandler, VipCategoryDeletedHandler, VipCategoryUpdatedHandler, SeasonPeriodDeletedHandler, SeasonPeriodUpdatedHandler, + ItemUpdatedHandler, + ItemDeletedHandler, + UserDeletedHandler, + UserUpdatedHandler, + TransactionDataService, + UserDataService, + ItemDataService, CouchService, ], }) diff --git a/src/modules/configuration/couch/data/services/couch.service.ts b/src/modules/configuration/couch/data/services/couch.service.ts index a91f88b..59cc403 100644 --- a/src/modules/configuration/couch/data/services/couch.service.ts +++ b/src/modules/configuration/couch/data/services/couch.service.ts @@ -1,23 +1,32 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, Logger } from '@nestjs/common'; import { DatabaseListen } from '../../constants'; import { EventBus } from '@nestjs/cqrs'; import { ChangeDocEvent } from '../../domain/events/change-doc.event'; +import { ConfigService } from '@nestjs/config'; + +import * as Nano from 'nano'; @Injectable() export class CouchService { - constructor(private eventBus: EventBus) {} + constructor( + private eventBus: EventBus, + private configService: ConfigService, + ) {} + + get nanoInstance() { + const couchConfiguration = this.configService.get('COUCHDB_CONFIG'); + return Nano(couchConfiguration); + } async onModuleInit() { - const nano = require('nano')( - 'http://root:password@db_pos_couch_staging:5984', - ); + const nano = this.nanoInstance; for (const database of DatabaseListen) { const db = nano.db.use(database); db.changesReader.start({ includeDocs: true }).on('change', (change) => { this.changeDoc(change, database); }); - console.log(`start listen database ${database}`); + Logger.log(`start listen database ${database}`, 'CouchService'); } } @@ -33,9 +42,7 @@ export class CouchService { public async createDoc(data, database) { try { - const nano = require('nano')( - 'http://root:password@db_pos_couch_staging:5984', - ); + const nano = this.nanoInstance; const db = nano.use(database); return await db.insert(data); } catch (error) {} @@ -43,9 +50,7 @@ export class CouchService { public async deleteDoc(data, database) { try { - const nano = require('nano')( - 'http://root:password@db_pos_couch_staging:5984', - ); + const nano = this.nanoInstance; const db = nano.use(database); const result = await db.get(data.id); await db.destroy(data.id, result._rev); @@ -54,9 +59,7 @@ export class CouchService { public async updateDoc(data, database) { try { - const nano = require('nano')( - 'http://root:password@db_pos_couch_staging:5984', - ); + const nano = this.nanoInstance; const db = nano.use(database); const result = await db.get(data.id); console.log(result, 'dsa'); diff --git a/src/modules/configuration/couch/domain/managers/booking.handler.ts b/src/modules/configuration/couch/domain/managers/booking.handler.ts new file mode 100644 index 0000000..4b64585 --- /dev/null +++ b/src/modules/configuration/couch/domain/managers/booking.handler.ts @@ -0,0 +1,73 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; +import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event'; +import { CouchService } from '../../data/services/couch.service'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { TransactionPaymentType } from 'src/modules/transaction/transaction/constants'; +import { mappingTransaction } from 'src/modules/transaction/transaction/domain/usecases/managers/helpers/mapping-transaction.helper'; +import { TransactionDeletedEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-deleted.event'; +import { TransactionUpdatedEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-updated.event'; + +@EventsHandler(TransactionDeletedEvent) +export class BookingDeletedEvent + implements IEventHandler +{ + constructor(private couchService: CouchService) {} + + async handle(event: TransactionDeletedEvent) { + await this.couchService.deleteDoc( + { + _id: event.data.id, + ...event.data.data, + }, + 'item', + ); + } +} + +@EventsHandler(TransactionChangeStatusEvent, TransactionUpdatedEvent) +export class BookingHandler + implements IEventHandler +{ + constructor( + private bookingService: TransactionDataService, + private couchService: CouchService, + ) {} + + async handle(event: TransactionChangeStatusEvent) { + const old_data = event.data.old; + const data = event.data.data; + + if (data.payment_type != TransactionPaymentType.COUNTER) return; + + const booking = await this.bookingService.getOneByOptions({ + where: { + id: data.id, + }, + relations: ['items'], + }); + + mappingTransaction(booking); + + if ( + old_data?.status != data.status && + [STATUS.PENDING, STATUS.ACTIVE].includes(data.status) + ) { + await this.couchService.createDoc( + { + _id: booking.id, + ...booking, + }, + 'booking', + ); + } else { + await this.couchService.updateDoc( + { + _id: booking.id, + ...booking, + }, + 'booking', + ); + } + } +} diff --git a/src/modules/configuration/couch/domain/managers/vip-code.handler.ts b/src/modules/configuration/couch/domain/managers/vip-code.handler.ts new file mode 100644 index 0000000..7efef35 --- /dev/null +++ b/src/modules/configuration/couch/domain/managers/vip-code.handler.ts @@ -0,0 +1,22 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { CouchService } from '../../data/services/couch.service'; +import { VipCodeCreatedEvent } from 'src/modules/transaction/vip-code/domain/entities/event/vip-code-created.event'; + +@EventsHandler(VipCodeCreatedEvent) +export class VipCodeCreatedHandler + implements IEventHandler +{ + constructor(private couchService: CouchService) {} + + async handle(event: VipCodeCreatedEvent) { + const data = event.data.data; + + await this.couchService.createDoc( + { + _id: data.id, + ...data, + }, + 'vip_code', + ); + } +} diff --git a/src/modules/configuration/couch/infrastructure/couch.controller.ts b/src/modules/configuration/couch/infrastructure/couch.controller.ts index 0e3704e..c7a9d5c 100644 --- a/src/modules/configuration/couch/infrastructure/couch.controller.ts +++ b/src/modules/configuration/couch/infrastructure/couch.controller.ts @@ -1,38 +1,44 @@ -import { Body, Controller, Get, Post } from '@nestjs/common'; +import { Body, Controller, Get, Injectable, Post } from '@nestjs/common'; import { ApiTags } from '@nestjs/swagger'; -import { Unprotected } from 'src/core/guards'; +import { Public } from 'src/core/guards'; import * as Nano from 'nano'; import { CreateUserPrivilegeDto } from 'src/modules/user-related/user-privilege/infrastructure/dto/create-user-privilege.dto'; +import { ConfigService } from '@nestjs/config'; @ApiTags(`couch`) @Controller('v1/couch') -@Unprotected() +@Public() +@Injectable() export class CouchDataController { + constructor(private configService: ConfigService) {} + + get nanoInstance() { + const couchConfiguration = this.configService.get('COUCHDB_CONFIG'); + return Nano(couchConfiguration); + } + @Post() async createDoc(@Body() entity: CreateUserPrivilegeDto) { try { - let n = Nano('http://admin:secret@127.0.0.1:5984'); - let db = await n.db.create(entity.name); - } catch (error) { - console.log(error, 'dsa'); - } + const n = this.nanoInstance; + await n.db.create(entity.name); + } catch (error) {} } @Post('doc') async createDocs(@Body() entity: CreateUserPrivilegeDto) { try { - const nano = require('nano')('http://admin:secret@127.0.0.1:5984'); + const nano = this.nanoInstance; const people = nano.db.use('string'); - console.log(await people.info()); - const data = { - id: '1212', - name: 'dsadas', - }; + // const data = { + // id: '1212', + // name: 'dsadas', + // }; // await people.insert(data) people.changesReader - .start() + .start({}) .on('change', (change) => { console.log(change); }) @@ -45,20 +51,17 @@ export class CouchDataController { .on('error', (e) => { console.error('error', e); }); - } catch (error) { - console.log(error, 'dsa'); - } + } catch (error) {} } @Get() async getDoc() { try { - let n = Nano('http://admin:secret@127.0.0.1:5984'); - const people = n.use('string'); + const n = this.nanoInstance; + const people = n.db.get('user'); + return people; // return people.get(); - } catch (error) { - console.log(error, 'dsa'); - } + } catch (error) {} } } diff --git a/src/modules/configuration/cron/cron.module.ts b/src/modules/configuration/cron/cron.module.ts new file mode 100644 index 0000000..45b82ce --- /dev/null +++ b/src/modules/configuration/cron/cron.module.ts @@ -0,0 +1,11 @@ +import { ConfigModule } from '@nestjs/config'; +import { MidnightCronManager } from './domain/managers/midnight-cron.manager'; +import { Module } from '@nestjs/common'; +import { CqrsModule } from '@nestjs/cqrs'; + +@Module({ + imports: [ConfigModule.forRoot(), CqrsModule], + controllers: [MidnightCronManager], + providers: [], +}) +export class CronModule {} diff --git a/src/modules/configuration/cron/domain/entities/cron-midnight.event.ts b/src/modules/configuration/cron/domain/entities/cron-midnight.event.ts new file mode 100644 index 0000000..c50b5b7 --- /dev/null +++ b/src/modules/configuration/cron/domain/entities/cron-midnight.event.ts @@ -0,0 +1,3 @@ +export class CronMidnightEvent { + // constructor(public readonly data: {}) {} +} diff --git a/src/modules/configuration/cron/domain/managers/midnight-cron.manager.ts b/src/modules/configuration/cron/domain/managers/midnight-cron.manager.ts new file mode 100644 index 0000000..3136421 --- /dev/null +++ b/src/modules/configuration/cron/domain/managers/midnight-cron.manager.ts @@ -0,0 +1,21 @@ +import { Controller } from '@nestjs/common'; +import { EventBus } from '@nestjs/cqrs'; +import { Cron } from '@nestjs/schedule'; +import { CronMidnightEvent } from '../entities/cron-midnight.event'; + +@Controller() +export class MidnightCronManager { + constructor(public eventBus: EventBus) {} + + @Cron(`${process.env.CRON_MIDNIGHT}`) + async scheduler(): Promise { + const local_time = new Date().toLocaleDateString('en-US', { + timeZone: 'Asia/Jakarta', + }); + const now = new Date(local_time).getTime(); + + console.log('Cron Event executed every 00:00 minutes.', now); + + this.eventBus.publishAll([new CronMidnightEvent()]); + } +} diff --git a/src/modules/configuration/log/domain/handlers/error-log.handler.ts b/src/modules/configuration/log/domain/handlers/error-log.handler.ts index 906b0b3..f1e3c58 100644 --- a/src/modules/configuration/log/domain/handlers/error-log.handler.ts +++ b/src/modules/configuration/log/domain/handlers/error-log.handler.ts @@ -6,5 +6,7 @@ import { ErrorLogService } from '../../data/services/error-log.service'; export class RecordErrorLogHandler implements IEventHandler { constructor(private dataservice: ErrorLogService) {} - async handle(event: RecordErrorLog) {} + async handle(event: RecordErrorLog) { + // TODO: Implement logic here + } } diff --git a/src/modules/configuration/log/domain/handlers/log.handler.ts b/src/modules/configuration/log/domain/handlers/log.handler.ts index 54cb92e..1e4fafb 100644 --- a/src/modules/configuration/log/domain/handlers/log.handler.ts +++ b/src/modules/configuration/log/domain/handlers/log.handler.ts @@ -3,5 +3,7 @@ import { RecordLog } from '../entities/log.event'; @EventsHandler(RecordLog) export class RecordLogHandler implements IEventHandler { - async handle(event: RecordLog) {} + async handle(event: RecordLog) { + // TODO: Implement logic here + } } diff --git a/src/modules/configuration/mail/domain/email-template/payment-confirmation-bank.html b/src/modules/configuration/mail/domain/email-template/payment-confirmation-bank.html new file mode 100644 index 0000000..daa023e --- /dev/null +++ b/src/modules/configuration/mail/domain/email-template/payment-confirmation-bank.html @@ -0,0 +1,413 @@ + + + + + + + Email Ibunda + + + + + + + + + + +
  +
+ + + + + + + +
+ + + + +
+

Halo {{customer_name}}

+

Pemesanan tiket telah berhasil dengan nomor invoice {{invoice_code}}, Silahkan lakukan pembayaran

+
+

+ PEMBAYARAN DAPAT MELALUI +

    + {{#each payment_methods}} +
  • +

    + {{issuer_name}}
    + Name: {{account_name}}
    + Number: {{account_number}} +

    +
  • + {{/each}} +
+

+
+
+ + + + + + +
+
 
+ + + \ No newline at end of file diff --git a/src/modules/configuration/mail/domain/email-template/payment-confirmation-midtrans.html b/src/modules/configuration/mail/domain/email-template/payment-confirmation-midtrans.html new file mode 100644 index 0000000..b1cc797 --- /dev/null +++ b/src/modules/configuration/mail/domain/email-template/payment-confirmation-midtrans.html @@ -0,0 +1,404 @@ + + + + + + + Email Ibunda + + + + + + + + + + +
  +
+ + + + + + + +
+ + + + + + + +
+

Halo {{customer_name}}

+

Pemesanan tiket telah berhasil dengan nomor invoice {{invoice_code}}, Silahkan lakukan pembayaran dengan klik button dibawah ini

+
+ Lanjutkan Pembayaran +
+
+ + + + + + +
+
 
+ + + \ No newline at end of file diff --git a/src/modules/configuration/mail/domain/handlers/payment-transaction.handler.ts b/src/modules/configuration/mail/domain/handlers/payment-transaction.handler.ts new file mode 100644 index 0000000..e3fb8ad --- /dev/null +++ b/src/modules/configuration/mail/domain/handlers/payment-transaction.handler.ts @@ -0,0 +1,61 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { PaymentMethodDataService } from 'src/modules/transaction/payment-method/data/services/payment-method-data.service'; +import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; +import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event'; +import { sendEmail } from '../helpers/send-email.helper'; +import { TransactionPaymentType } from 'src/modules/transaction/transaction/constants'; + +@EventsHandler(TransactionChangeStatusEvent) +export class PaymentTransactionHandler + implements IEventHandler +{ + constructor( + private dataService: TransactionDataService, + private paymentService: PaymentMethodDataService, + ) {} + + async handle(event: TransactionChangeStatusEvent) { + const data_id = event.data.id; + const old_data = event.data.old; + const current_data = event.data.data; + let payments = []; + + if ( + old_data.status == STATUS.DRAFT && + current_data.status == STATUS.PENDING && + current_data.payment_type != TransactionPaymentType.COUNTER && + !!current_data.customer_email + ) { + if (current_data.payment_type != TransactionPaymentType.MIDTRANS) { + payments = await this.paymentService.getManyByOptions({ + where: { + status: STATUS.ACTIVE, + }, + }); + } + + const transaction = await this.dataService.getOneByOptions({ + where: { + id: data_id, + }, + relations: ['items'], + }); + + try { + sendEmail( + [ + { + ...transaction, + email: transaction.customer_email, + payment_methods: payments, + }, + ], + 'Payment Confirmation', + ); + } catch (error) { + console.log(error); + } + } + } +} diff --git a/src/modules/configuration/mail/domain/helpers/send-email.helper.ts b/src/modules/configuration/mail/domain/helpers/send-email.helper.ts new file mode 100644 index 0000000..87dd1ee --- /dev/null +++ b/src/modules/configuration/mail/domain/helpers/send-email.helper.ts @@ -0,0 +1,50 @@ +import * as nodemailer from 'nodemailer'; +import * as handlebars from 'handlebars'; +import * as path from 'path'; +import * as fs from 'fs'; +import { TransactionPaymentType } from 'src/modules/transaction/transaction/constants'; + +export async function sendEmail(receivers, subject) { + const smtpTransport = nodemailer.createTransport({ + host: process.env.EMAIL_HOST, + port: process.env.EMAIL_POST, + auth: { + user: process.env.EMAIL_USER, + pass: process.env.EMAIL_TOKEN, + }, + }); + let templateName = 'payment-confirmation-bank'; + + for (const receiver of receivers) { + if (receiver.payment_type == TransactionPaymentType.MIDTRANS) + templateName = 'payment-confirmation-midtrans'; + + let templatePath = path.resolve( + __dirname, + `../email-template/${templateName}.html`, + ); + templatePath = templatePath.replace(/dist/g, 'src'); + const templateSource = fs.readFileSync(templatePath, 'utf8'); + + const template = handlebars.compile(templateSource); + const htmlToSend = template(receiver); + + const emailContext = { + from: 'no-reply@eigen.co.id', + to: receiver.email, + subject: subject, + html: htmlToSend, + attachDataUrls: true, + }; + + await new Promise((f) => setTimeout(f, 2000)); + + smtpTransport.sendMail(emailContext, function (err, data) { + if (err) { + console.log(`Error occurs on send to ${receiver.email}`); + } else { + console.log(`Email sent to ${receiver.email}`); + } + }); + } +} diff --git a/src/modules/configuration/mail/mail.module.ts b/src/modules/configuration/mail/mail.module.ts new file mode 100644 index 0000000..597fc9c --- /dev/null +++ b/src/modules/configuration/mail/mail.module.ts @@ -0,0 +1,28 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { CqrsModule } from '@nestjs/cqrs'; +import { PaymentMethodModel } from 'src/modules/transaction/payment-method/data/models/payment-method.model'; +import { PaymentMethodDataService } from 'src/modules/transaction/payment-method/data/services/payment-method-data.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; +import { PaymentTransactionHandler } from './domain/handlers/payment-transaction.handler'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature( + [PaymentMethodModel, TransactionModel], + CONNECTION_NAME.DEFAULT, + ), + CqrsModule, + ], + controllers: [], + providers: [ + PaymentTransactionHandler, + PaymentMethodDataService, + TransactionDataService, + ], +}) +export class MailModule {} diff --git a/src/modules/configuration/midtrans/data/services/midtrans.service.ts b/src/modules/configuration/midtrans/data/services/midtrans.service.ts new file mode 100644 index 0000000..5a3d658 --- /dev/null +++ b/src/modules/configuration/midtrans/data/services/midtrans.service.ts @@ -0,0 +1,26 @@ +import { Injectable } from '@nestjs/common'; +import { EventBus } from '@nestjs/cqrs'; +import { mappingMidtransTransaction } from '../../domain/usecases/helpers/mapping-transaction.helper'; +const midtransClient = require('midtrans-client'); + +@Injectable() +export class MidtransService { + constructor(private eventBus: EventBus) {} + + get midtransInstance() { + return new midtransClient.Snap({ + isProduction: false, + serverKey: process.env.MIDTRANS_SERVER_KEY, + clientKey: process.env.MIDTRANS_CLIENT_KEY, + }); + } + + async getStatus(orderId: string): Promise { + return await this.midtransInstance.transaction.status(orderId); + } + + async create(body): Promise { + const data = mappingMidtransTransaction(body); + return await this.midtransInstance.createTransaction(data); + } +} diff --git a/src/modules/configuration/midtrans/domain/entities/midtrans-callback.event.ts b/src/modules/configuration/midtrans/domain/entities/midtrans-callback.event.ts new file mode 100644 index 0000000..0b77294 --- /dev/null +++ b/src/modules/configuration/midtrans/domain/entities/midtrans-callback.event.ts @@ -0,0 +1,8 @@ +export class MidtransCallbackEvent { + constructor(public readonly data: IEventMidtrans) {} +} + +export interface IEventMidtrans { + id: string; + data: any; +} diff --git a/src/modules/configuration/midtrans/domain/usecases/helpers/mapping-transaction.helper.ts b/src/modules/configuration/midtrans/domain/usecases/helpers/mapping-transaction.helper.ts new file mode 100644 index 0000000..0e6cb4c --- /dev/null +++ b/src/modules/configuration/midtrans/domain/usecases/helpers/mapping-transaction.helper.ts @@ -0,0 +1,31 @@ +export function mappingMidtransTransaction(transaction) { + const item_details = transaction.items?.map((item) => { + return { + quantity: Number(item.qty), + price: Number(item.total_price), + name: item.item_name, + category: item.item_category_name, + }; + }); + + if (transaction.payment_discount_total) { + item_details.push({ + quantity: 1, + price: -Number(transaction.payment_discount_total), + name: 'discount', + }); + } + + return { + transaction_details: { + order_id: transaction.id, + gross_amount: Number(transaction.payment_total), + }, + item_details: item_details, + customer_details: { + first_name: transaction.customer_name, + email: transaction.customer_email, + phone: transaction.customer_phone, + }, + }; +} diff --git a/src/modules/configuration/midtrans/infrastructure/dto/midtrans.dto.ts b/src/modules/configuration/midtrans/infrastructure/dto/midtrans.dto.ts new file mode 100644 index 0000000..8e80c8f --- /dev/null +++ b/src/modules/configuration/midtrans/infrastructure/dto/midtrans.dto.ts @@ -0,0 +1,9 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class MidtransDto { + @ApiProperty({ + type: String, + example: '123', + }) + order_id: string; +} diff --git a/src/modules/configuration/midtrans/infrastructure/midtrans.controller.ts b/src/modules/configuration/midtrans/infrastructure/midtrans.controller.ts new file mode 100644 index 0000000..a00867a --- /dev/null +++ b/src/modules/configuration/midtrans/infrastructure/midtrans.controller.ts @@ -0,0 +1,36 @@ +import { Body, Controller, Get, Injectable, Param, Post } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { Public } from 'src/core/guards'; +import { MidtransService } from '../data/services/midtrans.service'; +import { EventBus } from '@nestjs/cqrs'; +import { MidtransCallbackEvent } from '../domain/entities/midtrans-callback.event'; +import { MidtransDto } from './dto/midtrans.dto'; + +@ApiTags(`midtrans`) +@Controller('v1/midtrans') +@Public() +@Injectable() +export class MidtransController { + constructor( + private dataService: MidtransService, + private eventBus: EventBus, + ) {} + + @Get(':id/status') + async getStatus(@Param('id') id: string) { + return await this.dataService.getStatus(id); + } + + @Post('callback') + async callback(@Body() callback: MidtransDto) { + const data = await this.dataService.getStatus(callback?.order_id); + this.eventBus.publishAll([ + new MidtransCallbackEvent({ + id: data.order_id, + data: data, + }), + ]); + console.log(`midtrans callback for order ${data.order_id}`); + return 'success listen callback'; + } +} diff --git a/src/modules/configuration/midtrans/midtrans.module.ts b/src/modules/configuration/midtrans/midtrans.module.ts new file mode 100644 index 0000000..755a639 --- /dev/null +++ b/src/modules/configuration/midtrans/midtrans.module.ts @@ -0,0 +1,14 @@ +import { ConfigModule } from '@nestjs/config'; +import { CqrsModule } from '@nestjs/cqrs'; +import { MidtransController } from './infrastructure/midtrans.controller'; +import { MidtransService } from './data/services/midtrans.service'; +import { Global, Module } from '@nestjs/common'; + +@Global() +@Module({ + imports: [ConfigModule.forRoot(), CqrsModule], + controllers: [MidtransController], + providers: [MidtransService], + exports: [MidtransService], +}) +export class MidtransModule {} diff --git a/src/modules/configuration/upload/domain/entities/upload.entity.ts b/src/modules/configuration/upload/domain/entities/upload.entity.ts new file mode 100644 index 0000000..2e97daa --- /dev/null +++ b/src/modules/configuration/upload/domain/entities/upload.entity.ts @@ -0,0 +1,5 @@ +export interface UploadEntity { + module: string; + sub_module: string; + file: any; +} diff --git a/src/modules/configuration/upload/infrastructure/dto/upload.dto.ts b/src/modules/configuration/upload/infrastructure/dto/upload.dto.ts new file mode 100644 index 0000000..f8bcd48 --- /dev/null +++ b/src/modules/configuration/upload/infrastructure/dto/upload.dto.ts @@ -0,0 +1,22 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { UploadEntity } from '../../domain/entities/upload.entity'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +export class UploadDto implements UploadEntity { + @ApiProperty({ + type: 'string', + required: true, + default: TABLE_NAME.ITEM, + }) + module: string; + + @ApiProperty({ + type: 'string', + required: false, + default: TABLE_NAME.ITEM, + }) + sub_module: string; + + @ApiProperty({ type: 'string', format: 'binary', required: true }) + file: any; +} diff --git a/src/modules/configuration/upload/infrastructure/upload.controller.ts b/src/modules/configuration/upload/infrastructure/upload.controller.ts new file mode 100644 index 0000000..d754545 --- /dev/null +++ b/src/modules/configuration/upload/infrastructure/upload.controller.ts @@ -0,0 +1,34 @@ +import { + Controller, + Post, + UseInterceptors, + Body, + UploadedFile, +} from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; +import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { UploadDto } from './dto/upload.dto'; +import { Public } from 'src/core/guards'; +import { StoreFileConfig } from 'src/core/helpers/path/upload-store-path.helper'; + +@ApiTags('uploads') +@Controller('uploads') +@Public(true) +export class UploadController { + constructor(private moduleRef: ModuleRef) {} + + @Post() + @ApiConsumes('multipart/form-data') + @ApiBody({ type: UploadDto }) + @UseInterceptors(FileInterceptor('file', StoreFileConfig)) + async storeFile( + @UploadedFile() file: Express.Multer.File, + @Body() body: UploadDto, + ): Promise { + return { + path: file.path, + full_path: `${process.env.ASSETS}${file.path}`, + }; + } +} diff --git a/src/modules/configuration/upload/upload.module.ts b/src/modules/configuration/upload/upload.module.ts new file mode 100644 index 0000000..ffcdc4b --- /dev/null +++ b/src/modules/configuration/upload/upload.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { MulterModule } from '@nestjs/platform-express'; +import { UploadController } from './infrastructure/upload.controller'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + MulterModule.register({ + dest: './uploads', + }), + ], + controllers: [UploadController], + providers: [], +}) +export class UploadModule {} diff --git a/src/modules/item-related/item-category/domain/usecases/item-category-data.orchestrator.ts b/src/modules/item-related/item-category/domain/usecases/item-category-data.orchestrator.ts index 7d356c0..916c8e5 100644 --- a/src/modules/item-related/item-category/domain/usecases/item-category-data.orchestrator.ts +++ b/src/modules/item-related/item-category/domain/usecases/item-category-data.orchestrator.ts @@ -49,7 +49,7 @@ export class ItemCategoryDataOrchestrator extends BaseDataTransactionOrchestrato return this.updateManager.getResult(); } - async delete(dataId): Promise { + async delete(dataId): Promise { this.deleteManager.setData(dataId); this.deleteManager.setService(this.serviceData, TABLE_NAME.ITEM_CATEGORY); await this.deleteManager.execute(); @@ -66,7 +66,7 @@ export class ItemCategoryDataOrchestrator extends BaseDataTransactionOrchestrato return this.batchDeleteManager.getResult(); } - async active(dataId): Promise { + async active(dataId): Promise { this.activeManager.setData(dataId, STATUS.ACTIVE); this.activeManager.setService(this.serviceData, TABLE_NAME.ITEM_CATEGORY); await this.activeManager.execute(); @@ -83,7 +83,7 @@ export class ItemCategoryDataOrchestrator extends BaseDataTransactionOrchestrato return this.batchActiveManager.getResult(); } - async confirm(dataId): Promise { + async confirm(dataId): Promise { this.confirmManager.setData(dataId, STATUS.ACTIVE); this.confirmManager.setService(this.serviceData, TABLE_NAME.ITEM_CATEGORY); await this.confirmManager.execute(); @@ -100,7 +100,7 @@ export class ItemCategoryDataOrchestrator extends BaseDataTransactionOrchestrato return this.batchConfirmManager.getResult(); } - async inactive(dataId): Promise { + async inactive(dataId): Promise { this.inactiveManager.setData(dataId, STATUS.INACTIVE); this.inactiveManager.setService(this.serviceData, TABLE_NAME.ITEM_CATEGORY); await this.inactiveManager.execute(); diff --git a/src/modules/item-related/item-category/domain/usecases/managers/create-item-category.manager.ts b/src/modules/item-related/item-category/domain/usecases/managers/create-item-category.manager.ts index 9007f3d..30725a3 100644 --- a/src/modules/item-related/item-category/domain/usecases/managers/create-item-category.manager.ts +++ b/src/modules/item-related/item-category/domain/usecases/managers/create-item-category.manager.ts @@ -22,7 +22,9 @@ export class CreateItemCategoryManager extends BaseCreateManager {} + async generateConfig(): Promise { + // TODO: Implement logic here + } get validateRelations(): validateRelations[] { return []; diff --git a/src/modules/item-related/item-category/infrastructure/item-category-data.controller.ts b/src/modules/item-related/item-category/infrastructure/item-category-data.controller.ts index 47c06bb..f8847dd 100644 --- a/src/modules/item-related/item-category/infrastructure/item-category-data.controller.ts +++ b/src/modules/item-related/item-category/infrastructure/item-category-data.controller.ts @@ -34,7 +34,7 @@ export class ItemCategoryDataController { } @Patch(':id/active') - async active(@Param('id') dataId: string): Promise { + async active(@Param('id') dataId: string): Promise { return await this.orchestrator.active(dataId); } @@ -44,7 +44,7 @@ export class ItemCategoryDataController { } @Patch(':id/confirm') - async confirm(@Param('id') dataId: string): Promise { + async confirm(@Param('id') dataId: string): Promise { return await this.orchestrator.confirm(dataId); } @@ -54,7 +54,7 @@ export class ItemCategoryDataController { } @Patch(':id/inactive') - async inactive(@Param('id') dataId: string): Promise { + async inactive(@Param('id') dataId: string): Promise { return await this.orchestrator.inactive(dataId); } @@ -72,7 +72,7 @@ export class ItemCategoryDataController { } @Delete(':id') - async delete(@Param('id') dataId: string): Promise { + async delete(@Param('id') dataId: string): Promise { return await this.orchestrator.delete(dataId); } } diff --git a/src/modules/item-related/item-rate/domain/usecases/item-rate-data.orchestrator.ts b/src/modules/item-related/item-rate/domain/usecases/item-rate-data.orchestrator.ts index 0745326..f383d6f 100644 --- a/src/modules/item-related/item-rate/domain/usecases/item-rate-data.orchestrator.ts +++ b/src/modules/item-related/item-rate/domain/usecases/item-rate-data.orchestrator.ts @@ -36,7 +36,7 @@ export class ItemRateDataOrchestrator extends BaseDataOrchestrator { + async delete(dataId): Promise { this.deleteManager.setData(dataId); this.deleteManager.setService(this.serviceData, TABLE_NAME.ITEM_RATE); await this.deleteManager.execute(); diff --git a/src/modules/item-related/item-rate/domain/usecases/managers/index-item-rate.manager.ts b/src/modules/item-related/item-rate/domain/usecases/managers/index-item-rate.manager.ts index 8d1559c..7c29f07 100644 --- a/src/modules/item-related/item-rate/domain/usecases/managers/index-item-rate.manager.ts +++ b/src/modules/item-related/item-rate/domain/usecases/managers/index-item-rate.manager.ts @@ -6,6 +6,7 @@ import { RelationParam, } from 'src/core/modules/domain/entities/base-filter.entity'; import { ItemEntity } from 'src/modules/item-related/item/domain/entities/item.entity'; +import { STATUS } from 'src/core/strings/constants/base.constants'; @Injectable() export class IndexItemRateManager extends BaseIndexManager { @@ -19,7 +20,7 @@ export class IndexItemRateManager extends BaseIndexManager { async afterProcess(): Promise { this.result.data?.map((item) => { - let prices = []; + const prices = []; for ( let d = new Date(this.filterParam.start_date); d <= new Date(this.filterParam.end_date); @@ -27,6 +28,7 @@ export class IndexItemRateManager extends BaseIndexManager { ) { const rate = item['item_rates']?.find( (rate) => + rate.season_period?.status == STATUS.ACTIVE && d >= new Date(rate.season_period.start_date) && d <= new Date(rate.season_period.end_date), ); @@ -70,6 +72,7 @@ export class IndexItemRateManager extends BaseIndexManager { get selects(): string[] { return [ `${this.tableName}.id`, + `${this.tableName}.status`, `${this.tableName}.created_at`, `${this.tableName}.name`, `${this.tableName}.base_price`, @@ -84,6 +87,7 @@ export class IndexItemRateManager extends BaseIndexManager { 'item_rates.price', 'season_period.id', + 'season_period.status', 'season_period.holiday_name', 'season_period.start_date', 'season_period.end_date', @@ -121,10 +125,11 @@ export class IndexItemRateManager extends BaseIndexManager { queryBuilder.andWhere(`${this.tableName}.tenant_id In (:...tenantIds)`, { tenantIds: this.filterParam.tenant_ids, }); - } else if (!this.filterParam.all_item) { - queryBuilder.andWhere(`${this.tableName}.tenant_id Is Null`); } + queryBuilder.andWhere(`${this.tableName}.status In (:...statuses)`, { + statuses: [STATUS.ACTIVE], + }); return queryBuilder; } } diff --git a/src/modules/item-related/item-rate/infrastructure/item-rate-data.controller.ts b/src/modules/item-related/item-rate/infrastructure/item-rate-data.controller.ts index 6a789db..82dfa2b 100644 --- a/src/modules/item-related/item-rate/infrastructure/item-rate-data.controller.ts +++ b/src/modules/item-related/item-rate/infrastructure/item-rate-data.controller.ts @@ -38,7 +38,7 @@ export class ItemRateDataController { } // @Delete(':id') - // async delete(@Param('id') dataId: string): Promise { + // async delete(@Param('id') dataId: string): Promise { // return await this.orchestrator.delete(dataId); // } } diff --git a/src/modules/item-related/item/data/models/item.model.ts b/src/modules/item-related/item/data/models/item.model.ts index 0b9bf82..61b645e 100644 --- a/src/modules/item-related/item/data/models/item.model.ts +++ b/src/modules/item-related/item/data/models/item.model.ts @@ -15,6 +15,7 @@ import { LimitType } from '../../constants'; import { ItemCategoryModel } from 'src/modules/item-related/item-category/data/models/item-category.model'; import { UserModel } from 'src/modules/user-related/user/data/models/user.model'; import { ItemRateModel } from 'src/modules/item-related/item-rate/data/models/item-rate.model'; +import { GateModel } from 'src/modules/web-information/gate/data/models/gate.model'; @Entity(TABLE_NAME.ITEM) export class ItemModel @@ -24,8 +25,8 @@ export class ItemModel @Column('varchar', { name: 'name' }) name: string; - @Column('varchar', { name: 'image', nullable: true }) - image: string; + @Column('varchar', { name: 'image_url', nullable: true }) + image_url: string; @Column('enum', { name: 'item_type', @@ -108,4 +109,11 @@ export class ItemModel onUpdate: 'CASCADE', }) item_rates: ItemRateModel[]; + + // relasi ke gate + @OneToMany(() => GateModel, (model) => model.item, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + gates: GateModel[]; } diff --git a/src/modules/item-related/item/domain/entities/filter-item.entity.ts b/src/modules/item-related/item/domain/entities/filter-item.entity.ts index 1bde43f..19a0b72 100644 --- a/src/modules/item-related/item/domain/entities/filter-item.entity.ts +++ b/src/modules/item-related/item/domain/entities/filter-item.entity.ts @@ -6,4 +6,5 @@ export interface FilterItemEntity extends BaseFilterEntity { limit_types: string[]; tenant_ids: string[]; all_item: boolean; + season_period_ids: string[]; } diff --git a/src/modules/item-related/item/domain/entities/item.entity.ts b/src/modules/item-related/item/domain/entities/item.entity.ts index b0ab24b..53620cd 100644 --- a/src/modules/item-related/item/domain/entities/item.entity.ts +++ b/src/modules/item-related/item/domain/entities/item.entity.ts @@ -5,7 +5,7 @@ import { LimitType } from '../../constants'; export interface ItemEntity extends BaseStatusEntity { name: string; item_type: ItemType; - image: string; + image_url: string; hpp: number; sales_margin: number; diff --git a/src/modules/item-related/item/domain/usecases/item-data.orchestrator.ts b/src/modules/item-related/item/domain/usecases/item-data.orchestrator.ts index d7efe8b..0c9296e 100644 --- a/src/modules/item-related/item/domain/usecases/item-data.orchestrator.ts +++ b/src/modules/item-related/item/domain/usecases/item-data.orchestrator.ts @@ -15,6 +15,8 @@ import { BatchInactiveItemManager } from './managers/batch-inactive-item.manager import { BatchActiveItemManager } from './managers/batch-active-item.manager'; import { BatchDeleteItemManager } from './managers/batch-delete-item.manager'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { UpdateItemRatePriceManager } from './managers/update-item-rate-price.manager'; +import { ItemRateReadService } from 'src/modules/item-related/item-rate/data/services/item-rate-read.service'; @Injectable() export class ItemDataOrchestrator extends BaseDataTransactionOrchestrator { @@ -28,8 +30,10 @@ export class ItemDataOrchestrator extends BaseDataTransactionOrchestrator { + async updatePrice(data): Promise { + this.updatePriceManager.setData(data); + this.updatePriceManager.setService(this.serviceRateData, TABLE_NAME.ITEM); + await this.updatePriceManager.execute(); + return this.updatePriceManager.getResult(); + } + + async delete(dataId, tenantId?: string): Promise { this.deleteManager.setData(dataId); this.deleteManager.setService(this.serviceData, TABLE_NAME.ITEM); await this.deleteManager.execute(); @@ -77,7 +88,7 @@ export class ItemDataOrchestrator extends BaseDataTransactionOrchestrator { + async active(dataId, tenantId?: string): Promise { this.activeManager.setData(dataId, STATUS.ACTIVE); this.activeManager.setService(this.serviceData, TABLE_NAME.ITEM); await this.activeManager.execute(); @@ -94,7 +105,7 @@ export class ItemDataOrchestrator extends BaseDataTransactionOrchestrator { + async confirm(dataId, tenantId?: string): Promise { this.confirmManager.setData(dataId, STATUS.ACTIVE); this.confirmManager.setService(this.serviceData, TABLE_NAME.ITEM); await this.confirmManager.execute(); @@ -111,7 +122,7 @@ export class ItemDataOrchestrator extends BaseDataTransactionOrchestrator { + async inactive(dataId, tenantId?: string): Promise { this.inactiveManager.setData(dataId, STATUS.INACTIVE); this.inactiveManager.setService(this.serviceData, TABLE_NAME.ITEM); await this.inactiveManager.execute(); diff --git a/src/modules/item-related/item/domain/usecases/managers/detail-item.manager.ts b/src/modules/item-related/item/domain/usecases/managers/detail-item.manager.ts index c1ed614..591ff7a 100644 --- a/src/modules/item-related/item/domain/usecases/managers/detail-item.manager.ts +++ b/src/modules/item-related/item/domain/usecases/managers/detail-item.manager.ts @@ -33,6 +33,7 @@ export class DetailItemManager extends BaseDetailManager { get selects(): string[] { return [ `${this.tableName}.id`, + `${this.tableName}.image_url`, `${this.tableName}.created_at`, `${this.tableName}.status`, `${this.tableName}.item_type`, diff --git a/src/modules/item-related/item/domain/usecases/managers/index-item-rates.manager.ts b/src/modules/item-related/item/domain/usecases/managers/index-item-rates.manager.ts index 8a453db..c18bc97 100644 --- a/src/modules/item-related/item/domain/usecases/managers/index-item-rates.manager.ts +++ b/src/modules/item-related/item/domain/usecases/managers/index-item-rates.manager.ts @@ -10,7 +10,7 @@ import { ItemRateEntity } from 'src/modules/item-related/item-rate/domain/entiti @Injectable() export class IndexItemRatesManager extends BaseIndexManager { async prepareData(): Promise { - this.filterParam.order_by = `${this.tableName}.id`; + this.filterParam.order_by = `season_period.id`; return; } @@ -38,9 +38,11 @@ export class IndexItemRatesManager extends BaseIndexManager { get selects(): string[] { return [ `${this.tableName}.id`, + `${this.tableName}.item_id`, `${this.tableName}.price`, `season_period.id`, + `season_period.priority`, `season_period.created_at`, `season_period.creator_name`, `season_period.editor_name`, @@ -68,6 +70,25 @@ export class IndexItemRatesManager extends BaseIndexManager { itemIds: this.filterParam.item_ids, }); } + + if (this.filterParam.season_period_ids) { + queryBuilder.andWhere(`season_period.id In (:...seasonIdss)`, { + seasonIdss: this.filterParam.season_period_ids, + }); + } + + if (this.filterParam.start_date) { + queryBuilder.andWhere(`season_period.start_date <= :inputStartDate`, { + inputStartDate: this.filterParam.end_date, + }); + + queryBuilder.andWhere(`season_period.end_date >= :inputEndDate`, { + inputEndDate: this.filterParam.start_date, + }); + } + + queryBuilder.addOrderBy('season_period.priority', 'ASC'); + return queryBuilder; } } diff --git a/src/modules/item-related/item/domain/usecases/managers/update-item-rate-price.manager.ts b/src/modules/item-related/item/domain/usecases/managers/update-item-rate-price.manager.ts new file mode 100644 index 0000000..e11f875 --- /dev/null +++ b/src/modules/item-related/item/domain/usecases/managers/update-item-rate-price.manager.ts @@ -0,0 +1,72 @@ +import { Injectable } from '@nestjs/common'; +import { BaseCustomManager } from 'src/core/modules/domain/usecase/managers/base-custom.manager'; +import { ItemEntity } from '../../entities/item.entity'; +import { EventTopics } from 'src/core/strings/constants/interface.constants'; +import { ItemModel } from '../../../data/models/item.model'; +import { In, LessThanOrEqual, MoreThanOrEqual } from 'typeorm'; + +@Injectable() +export class UpdateItemRatePriceManager extends BaseCustomManager { + protected rates = []; + get entityTarget(): any { + return ItemModel; + } + + get eventTopics(): EventTopics[] { + return []; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + let query; + const item_ids = this.data.items.map((item) => { + return item.item.id; + }); + + if (this.data.season_period_id) { + query = { + item_id: In(item_ids), + season_period: { + id: this.data.season_period_id, + }, + }; + } else { + query = { + item_id: In(item_ids), + season_period: { + start_date: MoreThanOrEqual(this.data.booking_date), + end_date: LessThanOrEqual(this.data.booking_date), + }, + }; + } + + this.rates = await this.dataService.getManyByOptions({ + where: query, + }); + return; + } + + async process(): Promise { + this.data.items.map((item) => { + const current_price = this.rates.find( + (rate) => rate.item_id == item.item.id, + ); + + Object.assign(item, { + total_price: current_price?.price ?? item.item.base_price, + }); + }); + return; + } + + async afterProcess(): Promise { + return; + } + + async getResult() { + return this.data.items; + } +} diff --git a/src/modules/item-related/item/infrastructure/dto/filter-item.dto.ts b/src/modules/item-related/item/infrastructure/dto/filter-item.dto.ts index 5a0d70f..07b9845 100644 --- a/src/modules/item-related/item/infrastructure/dto/filter-item.dto.ts +++ b/src/modules/item-related/item/infrastructure/dto/filter-item.dto.ts @@ -10,6 +10,12 @@ export class FilterItemDto extends BaseFilterDto implements FilterItemEntity { }) item_categories: string[]; + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + season_period_ids: string[]; + @ApiProperty({ type: ['string'], required: false }) @Transform((body) => { return Array.isArray(body.value) ? body.value : [body.value]; diff --git a/src/modules/item-related/item/infrastructure/dto/item.dto.ts b/src/modules/item-related/item/infrastructure/dto/item.dto.ts index cc2067e..dfdd8dd 100644 --- a/src/modules/item-related/item/infrastructure/dto/item.dto.ts +++ b/src/modules/item-related/item/infrastructure/dto/item.dto.ts @@ -29,7 +29,7 @@ export class ItemDto extends BaseStatusDto implements ItemEntity { }) @IsString() @ValidateIf((body) => body.image) - image: string; + image_url: string; @ApiProperty({ type: 'string', diff --git a/src/modules/item-related/item/infrastructure/dto/update-item-price.dto.ts b/src/modules/item-related/item/infrastructure/dto/update-item-price.dto.ts new file mode 100644 index 0000000..b8fa0f5 --- /dev/null +++ b/src/modules/item-related/item/infrastructure/dto/update-item-price.dto.ts @@ -0,0 +1,43 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class UpdateItemPriceDto { + @ApiProperty({ + type: [Object], + required: true, + example: [ + { + item: { + id: 'bee5c493-fb35-4ceb-b7a1-7bc3edb3c63b', + name: 'TEnant 2 wahana air', + item_type: 'wahana', + base_price: '100000', + hpp: '0', + tenant: { + id: 'e19a4637-d4db-48cc-89ce-501913d07cdd', + name: 'e19a4637-d4db-48cc-89ce-501913d07cdd', + share_margin: null, + }, + item_category: { + id: '88633772-ec34-4645-bc04-6cfdce6af0cf', + name: 'Wahana Air', + }, + }, + qty: 1, + total_price: '100000', + }, + ], + }) + items: Object[]; + + @ApiProperty({ + type: String, + example: 'uuid', + }) + season_period_id: string; + + @ApiProperty({ + type: Date, + example: '2024-08-17', + }) + booking_date: Date; +} diff --git a/src/modules/item-related/item/infrastructure/item-data.controller.ts b/src/modules/item-related/item/infrastructure/item-data.controller.ts index a347dfa..f416665 100644 --- a/src/modules/item-related/item/infrastructure/item-data.controller.ts +++ b/src/modules/item-related/item/infrastructure/item-data.controller.ts @@ -15,6 +15,7 @@ import { ItemEntity } from '../domain/entities/item.entity'; import { BatchResult } from 'src/core/response/domain/ok-response.interface'; import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto'; import { Public } from 'src/core/guards'; +import { UpdateItemPriceDto } from './dto/update-item-price.dto'; @ApiTags(`${MODULE_NAME.ITEM.split('-').join(' ')} - data`) @Controller(`v1/${MODULE_NAME.ITEM}`) @@ -28,13 +29,18 @@ export class ItemDataController { return await this.orchestrator.create(data); } + @Post('update-price') + async updatePrice(@Body() body: UpdateItemPriceDto): Promise { + return await this.orchestrator.updatePrice(body); + } + @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 { + async active(@Param('id') dataId: string): Promise { return await this.orchestrator.active(dataId); } @@ -44,7 +50,7 @@ export class ItemDataController { } @Patch(':id/confirm') - async confirm(@Param('id') dataId: string): Promise { + async confirm(@Param('id') dataId: string): Promise { return await this.orchestrator.confirm(dataId); } @@ -54,7 +60,7 @@ export class ItemDataController { } @Patch(':id/inactive') - async inactive(@Param('id') dataId: string): Promise { + async inactive(@Param('id') dataId: string): Promise { return await this.orchestrator.inactive(dataId); } @@ -72,7 +78,7 @@ export class ItemDataController { } @Delete(':id') - async delete(@Param('id') dataId: string): Promise { + async delete(@Param('id') dataId: string): Promise { return await this.orchestrator.delete(dataId); } } diff --git a/src/modules/item-related/item/item.module.ts b/src/modules/item-related/item/item.module.ts index 456f7a2..bb35be2 100644 --- a/src/modules/item-related/item/item.module.ts +++ b/src/modules/item-related/item/item.module.ts @@ -25,6 +25,7 @@ import { ItemModel } from './data/models/item.model'; import { ItemRateModel } from '../item-rate/data/models/item-rate.model'; import { ItemRateReadService } from '../item-rate/data/services/item-rate-read.service'; import { IndexItemRatesManager } from './domain/usecases/managers/index-item-rates.manager'; +import { UpdateItemRatePriceManager } from './domain/usecases/managers/update-item-rate-price.manager'; @Global() @Module({ @@ -51,6 +52,7 @@ import { IndexItemRatesManager } from './domain/usecases/managers/index-item-rat BatchActiveItemManager, BatchConfirmItemManager, BatchInactiveItemManager, + UpdateItemRatePriceManager, ItemDataService, ItemReadService, diff --git a/src/modules/reports/report-bookmark/report-bookmark.controller.ts b/src/modules/reports/report-bookmark/report-bookmark.controller.ts new file mode 100644 index 0000000..30e92a6 --- /dev/null +++ b/src/modules/reports/report-bookmark/report-bookmark.controller.ts @@ -0,0 +1,74 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Post, + Put, + Query, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { ExcludePrivilege, Public } from 'src/core/guards'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { + GetLabelReportBookmarkDto, + GetReportBookmarkDto, +} from '../shared/dto/report-bookmark.get.dto'; +import { CreateReportBookmarkDto } from '../shared/dto/report-bookmark.create.dto'; +import { ReportBookmarkService } from './report-bookmark.service'; + +@ApiTags(`${MODULE_NAME.REPORT_BOOKMARK.split('-').join(' ')}`) +@Controller(`v1/${MODULE_NAME.REPORT_BOOKMARK}`) +@Public(false) +@ApiBearerAuth('JWT') +export class ReportBookmarkController { + constructor(private service: ReportBookmarkService) {} + + @Post() + @ExcludePrivilege() + async create(@Body() body: CreateReportBookmarkDto) { + return await this.service.create(body); + } + + @Get() + @ExcludePrivilege() + async getAll(@Query() query: GetReportBookmarkDto) { + return await this.service.getAll(query); + } + + // @Get(':id') + // async get(@Param('id') id: string) { + // return await this.service.getOne(id); + // } + + @Get('label-history') + @ExcludePrivilege() + async getAllLabelHistory(@Query() query: GetLabelReportBookmarkDto) { + return await this.service.getAllLabelHistory(query); + } + + @Get('applied') + @ExcludePrivilege() + async currentApplied(@Query() query: GetLabelReportBookmarkDto) { + return await this.service.getCurrentAppliedBookmark(query); + } + + @Put('applied/:id') + @ExcludePrivilege() + async applied(@Param('id') id: string) { + return await this.service.applied(id); + } + + @Put('unapplied/:id') + @ExcludePrivilege() + async unapplied(@Param('id') id: string) { + return await this.service.unapplied(id); + } + + @Delete(':id') + @ExcludePrivilege() + async delete(@Param('id') id: string) { + return await this.service.delete(id); + } +} diff --git a/src/modules/reports/report-bookmark/report-bookmark.module.ts b/src/modules/reports/report-bookmark/report-bookmark.module.ts new file mode 100644 index 0000000..ed44501 --- /dev/null +++ b/src/modules/reports/report-bookmark/report-bookmark.module.ts @@ -0,0 +1,16 @@ +import { Module } from '@nestjs/common'; +import { ReportBookmarkController } from './report-bookmark.controller'; +import { ReportBookmarkService } from './report-bookmark.service'; +import { ConfigModule } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { ReportBookmarkModel } from '../shared/models/report-bookmark.model'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ReportBookmarkModel], CONNECTION_NAME.DEFAULT), + ], + controllers: [ReportBookmarkController], + providers: [ReportBookmarkService], +}) +export class ReportBookmarkModule {} diff --git a/src/modules/reports/report-bookmark/report-bookmark.service.ts b/src/modules/reports/report-bookmark/report-bookmark.service.ts new file mode 100644 index 0000000..7bd7823 --- /dev/null +++ b/src/modules/reports/report-bookmark/report-bookmark.service.ts @@ -0,0 +1,198 @@ +import { Inject, Injectable, Scope } from '@nestjs/common'; +import { BaseReportService } from '../shared/services/base-report.service'; +import { CreateReportBookmarkDto } from '../shared/dto/report-bookmark.create.dto'; +import { + GetLabelReportBookmarkDto, + GetReportBookmarkDto, +} from '../shared/dto/report-bookmark.get.dto'; +import { Repository } from 'typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; +import { ReportBookmarkModel } from '../shared/models/report-bookmark.model'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { UserProvider } from 'src/core/sessions'; + +@Injectable({ scope: Scope.REQUEST }) +export class ReportBookmarkService extends BaseReportService { + @Inject() + protected userProvider: UserProvider; + + constructor( + @InjectRepository(ReportBookmarkModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(); + } + + async create(body: CreateReportBookmarkDto) { + const newPayload = this.injectDefaultColumnCreate(body); + const result = await this.repo.save(newPayload); + if (body.applied) { + await this.appliedFalseBookmark(result.id); + } + return result; + } + + async getAll(query: GetReportBookmarkDto) { + const modelName = ReportBookmarkModel.name; + + const creator_id = this.getUser().id; + const unique_names = query.unique_names; + const group_names = query.group_names; + const types = query.types; + + const qb = this.repo + .createQueryBuilder(modelName) + .where((query) => { + if (unique_names) { + query.andWhere(`unique_name IN (:...unique_names)`, { unique_names }); + } + if (group_names) { + query.andWhere(`group_name IN (:...group_names)`, { group_names }); + } + + if (types) { + query.andWhere(`type IN (:...types)`, { types }); + } + + query.andWhere(`creator_id = :creator_id`, { creator_id }); + }) + .orderBy(`${modelName}.created_at`, 'DESC'); + + return await qb.getMany(); + } + + async getOne(id: string) { + return await this.repo.findOneBy({ id }); + } + + async getAllLabelHistory(query: GetLabelReportBookmarkDto) { + const modelName = ReportBookmarkModel.name; + + const creator_id = this.getUser().id; + const unique_names = query.unique_names; + const group_names = query.group_names; + const types = query.types; + + const qb = this.repo + .createQueryBuilder(modelName) + .select(`${modelName}.label`) + .where((query) => { + if (unique_names) { + query.andWhere(`unique_name IN (:...unique_names)`, { unique_names }); + } + if (group_names) { + query.andWhere(`group_name IN (:...group_names)`, { group_names }); + } + + if (types) { + query.andWhere(`type IN (:...types)`, { types }); + } + + query.andWhere(`creator_id = :creator_id`, { creator_id }); + }) + .distinct(true); + + const newData = await qb.getRawMany(); + return newData.map((el) => el.ReportBookmarkModel_label); + } + + async getCurrentAppliedBookmark(query: GetLabelReportBookmarkDto) { + const modelName = ReportBookmarkModel.name; + + const creator_id = this.getUser().id; + const unique_names = query.unique_names; + const group_names = query.group_names; + const types = query.types; + const qb = this.repo + .createQueryBuilder(modelName) + .where((query) => { + if (unique_names) { + query.andWhere(`unique_name IN (:...unique_names)`, { unique_names }); + } + if (group_names) { + query.andWhere(`group_name IN (:...group_names)`, { group_names }); + } + + if (types) { + query.andWhere(`type IN (:...types)`, { types }); + } + + query.andWhere(`creator_id = :creator_id`, { creator_id }); + query.andWhere(`applied = :applied`, { applied: true }); + }) + .orderBy(`${modelName}.created_at`, 'DESC'); + + return await qb.getMany(); + } + + async applied(id: string) { + await this.repo + .createQueryBuilder() + .update(ReportBookmarkModel) + .set({ applied: true }) + .where((query) => { + query.andWhere(`id = :id`, { id }); + }) + .execute(); + + const data = await this.appliedFalseBookmark(id); + const group_name = data.group_name; + const unique_name = data.unique_name; + const type = data.type; + + return await this.getAll({ + group_names: [group_name], + unique_names: [unique_name], + types: [type], + }); + } + + async unapplied(id: string) { + await this.repo + .createQueryBuilder() + .update(ReportBookmarkModel) + .set({ applied: false }) + .where((query) => { + query.andWhere(`id = :id`, { id }); + }) + .execute(); + + const data = await this.getOne(id); + const group_name = data.group_name; + const unique_name = data.unique_name; + const type = data.type; + + return await this.getAll({ + group_names: [group_name], + unique_names: [unique_name], + types: [type], + }); + } + + async delete(id: string) { + await this.repo.delete(id); + return { + success: true, + message: `Successfully deleted bookmark with id "${id}"`, + }; + } + + async appliedFalseBookmark(id: string) { + const data = await this.getOne(id); + const creator_id = data.creator_id; + const type = data.type; + + await this.repo + .createQueryBuilder() + .update(ReportBookmarkModel) + .set({ applied: false }) + .where((query) => { + query.andWhere(`id != :id`, { id }); + query.andWhere(`creator_id = :creator_id`, { creator_id }); + query.andWhere(`type = :type`, { type }); + }) + .execute(); + + return data; + } +} diff --git a/src/modules/reports/report-export/report-export.controller.ts b/src/modules/reports/report-export/report-export.controller.ts new file mode 100644 index 0000000..318786a --- /dev/null +++ b/src/modules/reports/report-export/report-export.controller.ts @@ -0,0 +1,58 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Post, + Query, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { ExcludePrivilege, Public } from 'src/core/guards'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { CreateReportExportDto } from '../shared/dto/report-export.create.dto'; +import { + GetReportExportDto, + GetReportExportFileNameDto, + GetReportExportProcessingDto, +} from '../shared/dto/report-export.get.dto'; +import { ReportExportService } from './report-export.service'; + +@ApiTags(`${MODULE_NAME.REPORT_EXPORT.split('-').join(' ')}`) +@Controller(`v1/${MODULE_NAME.REPORT_EXPORT}`) +@Public(false) +@ApiBearerAuth('JWT') +export class ReportExportController { + constructor(private service: ReportExportService) {} + + @Post() + @ExcludePrivilege() + async create(@Body() body: CreateReportExportDto) { + await this.service.create(body); + return { message: 'Processing request export data.' }; + } + + @Get() + @ExcludePrivilege() + async getAll(@Query() query: GetReportExportDto) { + return await this.service.getAll(query); + } + + @Delete(':id') + @ExcludePrivilege() + async delete(@Param('id') id: string) { + return await this.service.delete(id); + } + + @Get('processing') + @ExcludePrivilege() + async getAllProcessing(@Query() query: GetReportExportProcessingDto) { + return await this.service.getAllProcessing(query); + } + + @Get('filename-history') + @ExcludePrivilege() + async getListHistoryFileName(@Query() query: GetReportExportFileNameDto) { + return await this.service.getListHistoryFileName(query); + } +} diff --git a/src/modules/reports/report-export/report-export.module.ts b/src/modules/reports/report-export/report-export.module.ts new file mode 100644 index 0000000..0506d59 --- /dev/null +++ b/src/modules/reports/report-export/report-export.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { ReportExportController } from './report-export.controller'; +import { ReportExportService } from './report-export.service'; +import { ConfigModule } from '@nestjs/config'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ExportReportHistoryModel } from '../shared/models/export-report-history.model'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; + +@Module({ + imports: [ + TypeOrmModule.forFeature( + [ExportReportHistoryModel], + CONNECTION_NAME.DEFAULT, + ), + ], + controllers: [ReportExportController], + providers: [ReportExportService], +}) +export class ReportExportModule {} diff --git a/src/modules/reports/report-export/report-export.service.ts b/src/modules/reports/report-export/report-export.service.ts new file mode 100644 index 0000000..d55a687 --- /dev/null +++ b/src/modules/reports/report-export/report-export.service.ts @@ -0,0 +1,468 @@ +import { + Injectable, + Logger, + NotFoundException, + Scope, + UnprocessableEntityException, +} from '@nestjs/common'; +import { BaseReportService } from '../shared/services/base-report.service'; +import { CreateReportExportDto } from '../shared/dto/report-export.create.dto'; +import { + GetReportExportDto, + GetReportExportFileNameDto, + GetReportExportProcessingDto, +} from '../shared/dto/report-export.get.dto'; +import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; +import { ExportReportHistoryModel } from '../shared/models/export-report-history.model'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { DataSource, Repository } from 'typeorm'; +import { ReportConfigs } from '../shared/configs'; +import { ReportConfigEntity } from '../shared/entities/report-config.entity'; +import { ReportQueryBuilder, capitalizeEachWord } from '../shared/helpers'; +import { DATA_FORMAT, REPORT_HISTORY_STATUS } from '../shared/constant'; + +import * as fs from 'fs'; +import * as path from 'path'; +import * as ExcelJS from 'exceljs'; +import { roundingCurrency } from '../shared/helpers'; +import { createPaginationMeta } from 'src/core/response/domain/utils/pagination-meta.helper'; +import * as moment from 'moment'; + +@Injectable({ scope: Scope.REQUEST }) +export class ReportExportService extends BaseReportService { + private readonly logger = new Logger(ReportExportService.name); + private readonly exportLimitPartition = process.env.EXPORT_LIMIT_PARTITION + ? parseInt(process.env.EXPORT_LIMIT_PARTITION) + : 100; + + constructor( + @InjectDataSource(CONNECTION_NAME.DEFAULT) + private dataSource: DataSource, + + @InjectRepository(ExportReportHistoryModel, CONNECTION_NAME.DEFAULT) + private exportHistoryRepo: Repository, + ) { + super(); + } + + getReportConfigByUniqueName(group_name, unique_name): ReportConfigEntity { + return ReportConfigs.find( + (item) => + item.unique_name === unique_name && item.group_name === group_name, + ); + } + + changeTimeZone(date, timeZone) { + if (typeof date === 'string') { + return new Date( + new Date(date).toLocaleString('en-US', { + timeZone, + }), + ); + } + + return new Date( + date.toLocaleString('en-US', { + timeZone, + }), + ); + } + + async create(body: CreateReportExportDto) { + let historyDataID = undefined; + try { + const config = this.getReportConfigByUniqueName( + body.group_name, + body.unique_name, + ); + const query_model = body.query_model; + const columnState = body.column_state; + const timeZone = body.time_zone; + const fName = body.file_name; + const defaultData = this.injectDefaultColumnCreate({}); + + query_model.groupKeys = []; // set [] on report only for get all data; + const DATA_SOURCE_NAME = 'dataSource'; + + const builder = new ReportQueryBuilder(config, query_model); + const SQL_EXPORT = builder.getSqlExport(); + const SQL_EXPORT_COUNT = builder.getSqlCountExport(); + + const count = await this[`${DATA_SOURCE_NAME}`].query(SQL_EXPORT_COUNT); + const totalRow = parseInt(count[0].count); + + const limit = this.exportLimitPartition; + + const partitionOffset = Array.from( + { length: Math.ceil(totalRow / limit) }, + (_, i) => i * limit, + ); + + const defaultFileName = fName ?? config.label; + const fileName = `${defaultFileName} (${Number(new Date())})`; + + const exportHistory = { + id: undefined, + group_name: config.group_name, + unique_name: config.unique_name, + label: config.label, + file_name: defaultFileName, + file_url: null, + total_data: totalRow, + processing_data: 0, + status: REPORT_HISTORY_STATUS.PROCESSING, + last_process_offset: 0, + last_process_limit: limit, + query_export: '', + ...defaultData, + }; + + this.logger.verbose(`EXPORT ${fileName} VIA STREAMING`); + this.logger.verbose(`EXPORT_LIMIT_PARTITION = ${limit}`); + + const directory = './uploads/report-data/'; + const filePath = path.join(directory, fileName); + if (!fs.existsSync(directory)) { + fs.mkdirSync(directory, { recursive: true }); + } + const workbookOptions = { + filename: filePath + '.xlsx', + useStyles: true, + }; + + exportHistory.file_url = fileName + '.xlsx'; + + //save export history + const historySave: any = await this.exportHistoryRepo.save( + exportHistory as any, + ); + exportHistory.id = historySave.id; + historyDataID = historySave.id; + + // Create a new workbook + const workbook = new ExcelJS.stream.xlsx.WorkbookWriter(workbookOptions); + // Create a new worksheet + const reportSheet = workbook.addWorksheet('Report'); + + let configColumns = config.column_configs; + const rowGroups = query_model?.rowGroupCols ?? []; + + if (rowGroups.length > 0) { + const valueCol = query_model?.valueCols ?? []; + configColumns = [...rowGroups, ...valueCol].map((i) => { + return config.column_configs.find((conf) => conf.column === i.id); + }); + } + + const columnStateIds = [ + ...rowGroups.map((i) => i.id), + ...columnState.map((i) => i.colId), + ]; + + const newConfigColumn = columnStateIds + .map((i) => { + const findData = configColumns.find((col) => col.column === i); + return findData; + }) + .filter(Boolean); + + reportSheet.columns = newConfigColumn.map((conf) => { + const isDate = + conf.format === DATA_FORMAT.DATE_EPOCH || + conf.format === DATA_FORMAT.DATE_TIMESTAMP; + const isCurrency = + conf.format === DATA_FORMAT.CURRENCY || + conf.format === DATA_FORMAT.MINUS_CURRENCY; + const isNumber = conf.format === DATA_FORMAT.NUMBER; + + const excelColumn = { + header: conf.label, + key: conf.column, + width: 20, + }; + + if (isDate) { + Object.assign(excelColumn, { style: { numFmt: 'dd/mm/yyyy' } }); + } else if (isCurrency) { + Object.assign(excelColumn, { style: { numFmt: '0.00' } }); + } else if (isNumber) { + Object.assign(excelColumn, { style: { numFmt: '' } }); + } + + return excelColumn; + }); + + // Making first line in excel bold + reportSheet.getRow(1).eachCell((cell) => { + cell.font = { bold: true }; + }); + + for (const offset of partitionOffset) { + const limitSql = ` limit ${limit} offset ${offset} `; + const newSqlExport = SQL_EXPORT + limitSql; + + const data = await this[`${DATA_SOURCE_NAME}`].query(newSqlExport); + + //update export history + exportHistory.processing_data += data.length; + exportHistory.updated_at = Number(new Date()); + exportHistory.last_process_offset = offset; + exportHistory.query_export = newSqlExport; + + await this.exportHistoryRepo.save(exportHistory as any); + + for (const item of data) { + const realItem = {}; + for (const itemKey of Object.keys(item)) { + const confCol = configColumns.find((c) => c.column === itemKey); + const isStatus = confCol.format === DATA_FORMAT.STATUS; + const isDate = confCol.format === DATA_FORMAT.DATE_EPOCH; + const isDateTimestamp = + confCol.format === DATA_FORMAT.DATE_TIMESTAMP; + const isNumber = confCol.format === DATA_FORMAT.NUMBER; + const isCurrency = confCol.format === DATA_FORMAT.CURRENCY; + const isMinusCurrency = + confCol.format === DATA_FORMAT.MINUS_CURRENCY; + const isBoolean = confCol.format === DATA_FORMAT.BOOLEAN; + const isPercentage = confCol.format === DATA_FORMAT.PERCENTAGE; + + const isSetInitNull = isNumber || isCurrency || isMinusCurrency; + + const isTextUpperCase = + confCol.format === DATA_FORMAT.TEXT_UPPERCASE; + const isTextLowerCase = + confCol.format === DATA_FORMAT.TEXT_LOWERCASE; + + if (isStatus) { + if (item[itemKey]) { + Object.assign(realItem, { + [`${itemKey}`]: capitalizeEachWord( + item[itemKey].split('_').join(' '), + ), + }); + } + } else if (isDate) { + if (item[itemKey]) { + Object.assign(realItem, { + [`${itemKey}`]: this.changeTimeZone( + new Date(Number(item[itemKey])), + timeZone, + ), + }); + } + } else if (isDateTimestamp) { + if (item[itemKey]) { + Object.assign(realItem, { + [`${itemKey}`]: this.changeTimeZone( + new Date(item[itemKey]), + timeZone, + ), + }); + } + } else if (isSetInitNull) { + const realValue = item[itemKey] ?? 0; + let valueItem = realValue; + if (isCurrency) { + valueItem = roundingCurrency(realValue); + } else if (isMinusCurrency) { + valueItem = roundingCurrency(realValue) * -1; + } + + Object.assign(realItem, { [`${itemKey}`]: Number(valueItem) }); + } else if (isPercentage) { + const realValue = item[itemKey] + ? `${item[itemKey]}%` + : item[itemKey]; + Object.assign(realItem, { [`${itemKey}`]: realValue }); + } else if (isBoolean) { + let realValue = ''; + if (item[itemKey] === true || item[itemKey] === 1) + realValue = 'Yes'; + else if (item[itemKey] === false || item[itemKey] === 0) + realValue = 'No'; + Object.assign(realItem, { [`${itemKey}`]: realValue }); + } else if (isTextUpperCase) { + Object.assign(realItem, { + [`${itemKey}`]: item[itemKey]?.toUpperCase(), + }); + } else if (isTextLowerCase) { + Object.assign(realItem, { + [`${itemKey}`]: item[itemKey]?.toLowerCase(), + }); + } else { + Object.assign(realItem, { [`${itemKey}`]: item[itemKey] }); + } + } + + reportSheet.addRow(realItem).commit(); + } + } + + reportSheet.commit(); + workbook.commit(); + + // update file_url on data export history using scrip bellow + // exportHistory.file_url = fileName + '.xlsx'; + exportHistory.total_data = exportHistory.processing_data; + exportHistory.status = 'done'; + exportHistory.updated_at = Number(new Date()); + await this.exportHistoryRepo.save(exportHistory as any); + } catch (error) { + this.exportHistoryRepo.update(historyDataID, { + status: REPORT_HISTORY_STATUS.FAILED, + }); + this.logger.error(error); + return new Error(error); + } + } + + async getAll(query: GetReportExportDto) { + const modelName = ExportReportHistoryModel.name; + + const page = query.page; + const limit = query.limit; + + const creator_id = this.getUser().id; + const group_names = query.group_names; + const unique_names = query.unique_names; + const statuses = query.statuses; + + const qb = this.exportHistoryRepo + .createQueryBuilder(modelName) + .where((query) => { + if (unique_names) { + query.andWhere(`unique_name IN (:...unique_names)`, { unique_names }); + } + if (group_names) { + query.andWhere(`group_name IN (:...group_names)`, { group_names }); + } + + if (statuses) { + query.andWhere(`status IN (:...statuses)`, { statuses }); + } + + query.andWhere(`creator_id = :creator_id`, { creator_id }); + }) + .orderBy(`${modelName}.created_at`, 'DESC'); + + const [data, total] = await qb + .take(+limit) + .skip(+limit * +page - +limit) + .getManyAndCount(); + + const meta = createPaginationMeta(page, limit, data.length, total); + + return { data, meta }; + } + + async delete(id: string) { + const findData = await this.exportHistoryRepo.findOneBy({ id }); + if (findData && findData?.file_url) { + try { + const directory = './uploads/report-data/'; + const filePath = path.join(directory, findData.file_url); + if (fs.existsSync(filePath)) { + fs.unlinkSync(filePath); + await this.exportHistoryRepo.delete(id); + this.logger.warn( + `Successfully deleted the ${findData.file_url} file`, + ); + return { success: true, message: 'File deleted successfully' }; + } else { + throw new NotFoundException('File not found'); + } + } catch (error) { + throw new UnprocessableEntityException('File could not be deleted'); + } + } + throw new UnprocessableEntityException(); + } + + async updateFailedData(group_names, unique_names) { + const creator_id = this.getUser().id; + const aMinutesAgo = moment().subtract(5, 'minutes').valueOf(); + + await this.exportHistoryRepo + .createQueryBuilder() + .update(ExportReportHistoryModel) + .set({ + status: REPORT_HISTORY_STATUS.FAILED, + updated_at: moment().valueOf(), + }) + .where((query) => { + if (group_names) { + query.andWhere(`group_name IN (:...group_names)`, { group_names }); + } + if (unique_names) { + query.andWhere(`unique_name IN (:...unique_names)`, { unique_names }); + } + query.andWhere(`status = :status`, { + status: REPORT_HISTORY_STATUS.PROCESSING, + }); + query.andWhere(`updated_at < :aMinutesAgo`, { aMinutesAgo }); + query.andWhere(`creator_id = :creator_id`, { creator_id }); + }) + .execute(); + } + + async getAllProcessing(query: GetReportExportProcessingDto) { + const creator_id = this.getUser().id; + const modelName = ExportReportHistoryModel.name; + + const group_names = query.group_names; + const unique_names = query.unique_names; + + await this.updateFailedData(group_names, unique_names); + const aMinutesAgo = moment().subtract(100, 'seconds').valueOf(); + + const qb = this.exportHistoryRepo + .createQueryBuilder(modelName) + .where((query) => { + if (group_names) { + query.andWhere(`unique_name IN (:...unique_names)`, { unique_names }); + } + if (unique_names) { + query.andWhere(`group_name IN (:...group_names)`, { group_names }); + } + + query.andWhere(`status IN (:...status)`, { + status: ['processing', 'failed', 'done'], + }); + query.andWhere(`updated_at > :aMinutesAgo`, { aMinutesAgo }); + query.andWhere(`creator_id = :creator_id`, { creator_id }); + }) + .orderBy(`${modelName}.created_at`, 'DESC'); + + const data = await qb.getMany(); + + return { + data: data, + }; + } + + async getListHistoryFileName(query: GetReportExportFileNameDto) { + const modelName = ExportReportHistoryModel.name; + + const creator_id = this.getUser().id; + const unique_names = query.unique_names; + const group_names = query.group_names; + + const qb = this.exportHistoryRepo + .createQueryBuilder(modelName) + .select(`${modelName}.file_name`) + .where((query) => { + if (unique_names) { + query.andWhere(`unique_name IN (:...unique_names)`, { unique_names }); + } + if (group_names) { + query.andWhere(`group_name IN (:...group_names)`, { group_names }); + } + + query.andWhere(`creator_id = :creator_id`, { creator_id }); + }) + .distinct(true); + + const newData = await qb.getRawMany(); + return newData.map((el) => el.ExportReportHistoryModel_file_name); + } +} diff --git a/src/modules/reports/report/report.controller.ts b/src/modules/reports/report/report.controller.ts new file mode 100644 index 0000000..26f3543 --- /dev/null +++ b/src/modules/reports/report/report.controller.ts @@ -0,0 +1,32 @@ +import { Body, Controller, Get, Post, Query } from '@nestjs/common'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { ExcludePrivilege, Public } from 'src/core/guards'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { ReportService } from './report.service'; +import { GetReportConfigDto } from '../shared/dto/report-config.get.dto'; +import { GetReportDataDto } from '../shared/dto/report-data.get.dto'; + +@ApiTags(`${MODULE_NAME.REPORT.split('-').join(' ')}`) +@Controller(`v1/${MODULE_NAME.REPORT}`) +@Public(false) +@ApiBearerAuth('JWT') +export class ReportController { + constructor(private service: ReportService) {} + @Get('config') + @ExcludePrivilege() + async getReportConfig(@Query() query: GetReportConfigDto) { + return await this.service.getReportConfig(query); + } + + @Post('data') + @ExcludePrivilege() + async getReportData(@Body() body: GetReportDataDto) { + return await this.service.getReportData(body); + } + + @Post('meta') + @ExcludePrivilege() + async getReportMeta(@Body() body: GetReportDataDto) { + return await this.service.getReportMeta(body); + } +} diff --git a/src/modules/reports/report/report.module.ts b/src/modules/reports/report/report.module.ts new file mode 100644 index 0000000..48b0de8 --- /dev/null +++ b/src/modules/reports/report/report.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { ReportController } from './report.controller'; +import { ReportService } from './report.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ExportReportHistoryModel } from '../shared/models/export-report-history.model'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { ReportBookmarkModel } from '../shared/models/report-bookmark.model'; + +@Module({ + imports: [ + TypeOrmModule.forFeature( + [ExportReportHistoryModel, ReportBookmarkModel], + CONNECTION_NAME.DEFAULT, + ), + ], + controllers: [ReportController], + providers: [ReportService], +}) +export class ReportModule {} diff --git a/src/modules/reports/report/report.service.ts b/src/modules/reports/report/report.service.ts new file mode 100644 index 0000000..3613ca1 --- /dev/null +++ b/src/modules/reports/report/report.service.ts @@ -0,0 +1,202 @@ +import { Injectable, Logger, Scope } from '@nestjs/common'; +import { BaseReportService } from '../shared/services/base-report.service'; +import { GetReportConfigDto } from '../shared/dto/report-config.get.dto'; +import { GetReportDataDto } from '../shared/dto/report-data.get.dto'; +import { ReportConfigs } from '../shared/configs'; +import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { DataSource, Repository } from 'typeorm'; +import { ReportConfigEntity } from '../shared/entities/report-config.entity'; +import { ReportQueryBuilder } from '../shared/helpers'; +import { DATA_FORMAT, REPORT_BOOKMARK_TYPE } from '../shared/constant'; +import { roundingCurrency } from '../shared/helpers'; +import { ReportBookmarkModel } from '../shared/models/report-bookmark.model'; + +@Injectable({ scope: Scope.REQUEST }) +export class ReportService extends BaseReportService { + private readonly logger = new Logger(ReportService.name); + + constructor( + @InjectDataSource(CONNECTION_NAME.DEFAULT) + private dataSource: DataSource, + + @InjectRepository(ReportBookmarkModel, CONNECTION_NAME.DEFAULT) + private bookmarkRepo: Repository, + ) { + super(); + } + + async getReportConfig(query: GetReportConfigDto) { + const { unique_names = [], group_names = [] } = query; + + let configs = ReportConfigs; + if (group_names.length > 0) { + configs = configs.filter((item) => group_names.includes(item.group_name)); + } + + if (unique_names.length > 0) { + configs = configs.filter((item) => + unique_names.includes(item.unique_name), + ); + } + const groups = Array.from(new Set(configs?.map((item) => item.group_name))); + const names = Array.from(new Set(configs?.map((item) => item.unique_name))); + + const modelName = ReportBookmarkModel.name; + const creator_id = this.getUser().id; + + const bookmarkConfigs = await this.bookmarkRepo + .createQueryBuilder(modelName) + .where((query) => { + if (names.length > 0) { + query.andWhere(`group_name IN (:...groups)`, { groups }); + } + if (groups.length > 0) { + query.andWhere(`unique_name IN (:...names)`, { names }); + } + + query.andWhere(`creator_id = :creator_id`, { creator_id }); + query.andWhere(`applied = :applied`, { applied: true }); + }) + .getMany(); + + return configs.map((item) => { + const active_filter = bookmarkConfigs.find( + (conf) => + conf.group_name === item.group_name && + conf.unique_name === item.unique_name && + conf.type === REPORT_BOOKMARK_TYPE.FILTER_TABLE, + ); + + const active_table_config = bookmarkConfigs.find( + (conf) => + conf.group_name === item.group_name && + conf.unique_name === item.unique_name && + conf.type === REPORT_BOOKMARK_TYPE.TABLE_CONFIG, + ); + return { + ...item, + active_filter: active_filter ?? {}, + active_table_config: active_table_config ?? {}, + }; + }); + } + + getReportConfigByUniqueName(group_name, unique_name): ReportConfigEntity { + return ReportConfigs.find( + (item) => + item.unique_name === unique_name && item.group_name === group_name, + ); + } + + async getReportData(body: GetReportDataDto) { + try { + const queryModel = body.query_model; + const reportConfig = this.getReportConfigByUniqueName( + body.group_name, + body.unique_name, + ); + const builder = new ReportQueryBuilder(reportConfig, queryModel); + const SQL = builder.getSql(); + const queryResult = await this.dataSource.query(SQL); + + const realData = []; + const configColumns = reportConfig.column_configs; + + for (const item of queryResult) { + const realItem = {}; + for (const itemKey of Object.keys(item)) { + if (itemKey === 'count_child_group') { + const realValue = item[itemKey] ?? 0; + Object.assign(realItem, { [`${itemKey}`]: Number(realValue) }); + } else { + const confCol = configColumns.find((c) => c.column === itemKey); + const isNumber = confCol.format === DATA_FORMAT.NUMBER; + const isCurrency = confCol.format === DATA_FORMAT.CURRENCY; + const isMinusCurrency = + confCol.format === DATA_FORMAT.MINUS_CURRENCY; + const isBoolean = confCol.format === DATA_FORMAT.BOOLEAN; + const isPercentage = confCol.format === DATA_FORMAT.PERCENTAGE; + const isSetInitNull = isNumber || isCurrency || isMinusCurrency; + const isTextUpperCase = + confCol.format === DATA_FORMAT.TEXT_UPPERCASE; + const isTextLowerCase = + confCol.format === DATA_FORMAT.TEXT_LOWERCASE; + + if (isSetInitNull) { + const realValue = item[itemKey] ?? 0; + if (isCurrency) { + Object.assign(realItem, { + [`${itemKey}`]: roundingCurrency(realValue), + }); + } else if (isMinusCurrency) { + Object.assign(realItem, { + [`${itemKey}`]: roundingCurrency(realValue) * -1, + }); + } else { + Object.assign(realItem, { [`${itemKey}`]: realValue }); + } + } else if (isPercentage) { + const realValue = item[itemKey] + ? `${item[itemKey]}%` + : item[itemKey]; + Object.assign(realItem, { [`${itemKey}`]: realValue }); + } else if (isBoolean) { + let realValue = ''; + if (item[itemKey] === true || item[itemKey] === 1) + realValue = 'Yes'; + else if (item[itemKey] === false || item[itemKey] === 0) + realValue = 'No'; + Object.assign(realItem, { [`${itemKey}`]: realValue }); + } else if (isTextUpperCase) { + Object.assign(realItem, { + [`${itemKey}`]: item[itemKey]?.toUpperCase(), + }); + } else if (isTextLowerCase) { + Object.assign(realItem, { + [`${itemKey}`]: item[itemKey]?.toLowerCase(), + }); + } else { + Object.assign(realItem, { [`${itemKey}`]: item[itemKey] }); + } + } + } + realData.push(realItem); + } + + return realData; + } catch (error) { + this.logger.error(error); + throw error; + } + } + + async getReportMeta(body: GetReportDataDto) { + try { + const queryModel = body.query_model; + const reportConfig = this.getReportConfigByUniqueName( + body.group_name, + body.unique_name, + ); + const builder = new ReportQueryBuilder(reportConfig, queryModel); + const SQL_COUNT = builder.getSqlCount(); + const queryResult = await this.dataSource.query(SQL_COUNT); + + const totalRow = parseInt(queryResult[0].count); + const startRow = queryModel.startRow; + const endRow = queryModel.endRow; + const pageSize = endRow - startRow; + + const meta = { + total_row: totalRow, + limit: pageSize, + offset: startRow, + }; + + return meta; + } catch (error) { + this.logger.error(error); + throw error; + } + } +} diff --git a/src/modules/reports/shared/configs/index.ts b/src/modules/reports/shared/configs/index.ts new file mode 100644 index 0000000..dc19755 --- /dev/null +++ b/src/modules/reports/shared/configs/index.ts @@ -0,0 +1,8 @@ +import { ReportConfigEntity } from '../entities/report-config.entity'; +import { TransactionReportConfig } from './transaction-report'; +import { TenantReportConfig } from './tenant-report'; + +export const ReportConfigs: ReportConfigEntity[] = [ + ...TransactionReportConfig, + ...TenantReportConfig, +]; diff --git a/src/modules/reports/shared/configs/tenant-report/configs/sample.report.ts b/src/modules/reports/shared/configs/tenant-report/configs/sample.report.ts new file mode 100644 index 0000000..6718cff --- /dev/null +++ b/src/modules/reports/shared/configs/tenant-report/configs/sample.report.ts @@ -0,0 +1,39 @@ +import { DATA_FORMAT, DATA_TYPE, REPORT_GROUP } from '../../../constant'; +import { ReportConfigEntity } from '../../../entities/report-config.entity'; + +export default { + group_name: REPORT_GROUP.tenant_report, + unique_name: `${REPORT_GROUP.tenant_report}__sample`, + label: 'Sample Tenant Report', + table_schema: 'season_types main', + main_table_alias: 'main', + defaultOrderBy: [], + lowLevelOrderBy: [], + filter_period_config: { + hidden: true, + }, + column_configs: [ + { + column: 'main__created_at', + query: 'main.created_at', + label: 'Created Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__updated_at', + query: 'main.updated_at', + label: 'Updated Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__name', + query: 'main.name', + label: 'Name', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + ], + filter_configs: [], +}; diff --git a/src/modules/reports/shared/configs/tenant-report/index.ts b/src/modules/reports/shared/configs/tenant-report/index.ts new file mode 100644 index 0000000..ceee528 --- /dev/null +++ b/src/modules/reports/shared/configs/tenant-report/index.ts @@ -0,0 +1,6 @@ +import { ReportConfigEntity } from '../../entities/report-config.entity'; +import SampleReport from './configs/sample.report'; + +export const TenantReportConfig: ReportConfigEntity[] = [ + // SampleReport +]; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/booking.ts b/src/modules/reports/shared/configs/transaction-report/configs/booking.ts new file mode 100644 index 0000000..cbc8a36 --- /dev/null +++ b/src/modules/reports/shared/configs/transaction-report/configs/booking.ts @@ -0,0 +1,193 @@ +import { TransactionType } from 'src/modules/transaction/transaction/constants'; +import { + DATA_FORMAT, + DATA_TYPE, + FILTER_FIELD_TYPE, + FILTER_TYPE, + REPORT_GROUP, +} from '../../../constant'; +import { ReportConfigEntity } from '../../../entities/report-config.entity'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +export default { + group_name: REPORT_GROUP.transaction_report, + unique_name: `${REPORT_GROUP.transaction_report}__booking`, + label: 'Pemesanan', + table_schema: `transactions AS main + LEFT JOIN refunds refund ON refund.transaction_id = main.id`, + main_table_alias: 'main', + defaultOrderBy: [], + lowLevelOrderBy: [], + filter_period_config: { + hidden: true, + }, + + column_configs: [ + { + column: 'main__status', + query: 'main.status', + label: 'Status', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.STATUS, + }, + { + column: 'main__booking_date', + query: 'main.booking_date', + label: 'Tgl. Booking', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_TIMESTAMP, + date_format: 'DD/MM/YYYY', + }, + { + column: 'main__no_of_group', + query: 'main.no_of_group', + label: 'Total Group', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.NUMBER, + }, + { + column: 'main__type', + query: 'main.type', + label: 'Sumber', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__payment_total', + query: 'main.payment_total', + label: 'Total Invoice', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'main__customer_type', + query: 'main.customer_type', + label: 'Tipe', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__customer_name', + query: 'main.customer_name', + label: 'Kontak', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__customer_phone', + query: 'main.customer_phone', + label: 'Telepon', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__customer_description', + query: 'main.customer_description', + label: 'Deskripsi', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__payment_type', + query: 'main.payment_type', + label: 'Tipe Pembayaran', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__payment_type_method_name', + query: 'main.payment_type_method_name', + label: 'Bank', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + + { + column: 'main__invoice_date', + query: 'main.invoice_date', + label: 'Tgl. Invoice', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_TIMESTAMP, + date_format: 'DD/MM/YYYY', + }, + { + column: 'main__invoice_code', + query: 'main.invoice_code', + label: 'Kode Invoice', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__settlement_date', + query: 'main.settlement_date', + label: 'Tgl Settlement', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_TIMESTAMP, + date_format: 'DD/MM/YYYY', + }, + { + column: 'refund__request_date', + query: 'refund.request_date', + label: 'Request Refund', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_TIMESTAMP, + date_format: 'DD/MM/YYYY', + }, + { + column: 'refund__code', + query: 'refund.code', + label: 'Kode Refund', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'refund__refund_date', + query: 'refund.refund_date', + label: 'Tgl. Refund', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_TIMESTAMP, + date_format: 'DD/MM/YYYY', + }, + + { + column: 'main__creator_name', + query: 'main.creator_name', + label: 'Dibuat Oleh', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__updated_at', + query: 'main.updated_at', + label: 'Tgl Update', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + ], + filter_configs: [ + { + filed_label: 'Status', + filter_column: 'main__status', + field_type: FILTER_FIELD_TYPE.select, + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + select_custom_options: [ + STATUS.DRAFT, + STATUS.WAITING, + STATUS.PENDING, + STATUS.SETTLED, + STATUS.REJECTED, + ], + }, + { + filed_label: 'Sumber', + filter_column: 'main__type', + field_type: FILTER_FIELD_TYPE.select, + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + select_custom_options: [ + TransactionType.ADMIN, + TransactionType.COUNTER, + TransactionType.ONLINE, + ], + }, + ], +}; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/cash-withdrawals.ts b/src/modules/reports/shared/configs/transaction-report/configs/cash-withdrawals.ts new file mode 100644 index 0000000..48616b1 --- /dev/null +++ b/src/modules/reports/shared/configs/transaction-report/configs/cash-withdrawals.ts @@ -0,0 +1,40 @@ +import { DATA_FORMAT, DATA_TYPE, REPORT_GROUP } from '../../../constant'; +import { ReportConfigEntity } from '../../../entities/report-config.entity'; + +export default { + group_name: REPORT_GROUP.transaction_report, + unique_name: `${REPORT_GROUP.transaction_report}__cash_withdrawals`, + label: 'Penarikan Kas', + table_schema: 'season_types main', + main_table_alias: 'main', + defaultOrderBy: [], + lowLevelOrderBy: [], + filter_period_config: { + hidden: true, + }, + + column_configs: [ + { + column: 'main__created_at', + query: 'main.created_at', + label: 'Created Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__updated_at', + query: 'main.updated_at', + label: 'Updated Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__name', + query: 'main.name', + label: 'Name', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + ], + filter_configs: [], +}; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/cashier-log.ts b/src/modules/reports/shared/configs/transaction-report/configs/cashier-log.ts new file mode 100644 index 0000000..5ad5d52 --- /dev/null +++ b/src/modules/reports/shared/configs/transaction-report/configs/cashier-log.ts @@ -0,0 +1,40 @@ +import { DATA_FORMAT, DATA_TYPE, REPORT_GROUP } from '../../../constant'; +import { ReportConfigEntity } from '../../../entities/report-config.entity'; + +export default { + group_name: REPORT_GROUP.transaction_report, + unique_name: `${REPORT_GROUP.transaction_report}__cashier_log`, + label: 'Kasir Log', + table_schema: 'season_types main', + main_table_alias: 'main', + defaultOrderBy: [], + lowLevelOrderBy: [], + filter_period_config: { + hidden: true, + }, + + column_configs: [ + { + column: 'main__created_at', + query: 'main.created_at', + label: 'Created Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__updated_at', + query: 'main.updated_at', + label: 'Updated Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__name', + query: 'main.name', + label: 'Name', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + ], + filter_configs: [], +}; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/income.ts b/src/modules/reports/shared/configs/transaction-report/configs/income.ts new file mode 100644 index 0000000..551b235 --- /dev/null +++ b/src/modules/reports/shared/configs/transaction-report/configs/income.ts @@ -0,0 +1,67 @@ +import { + DATA_FORMAT, + DATA_TYPE, + FILTER_FIELD_TYPE, + FILTER_TYPE, + REPORT_GROUP, +} from '../../../constant'; +import { ReportConfigEntity } from '../../../entities/report-config.entity'; + +export default { + group_name: REPORT_GROUP.transaction_report, + unique_name: `${REPORT_GROUP.transaction_report}__income`, + label: 'Pendapatan', + table_schema: 'season_types main', + main_table_alias: 'main', + defaultOrderBy: [], + lowLevelOrderBy: [], + filter_period_config: { + hidden: true, + }, + + column_configs: [ + { + column: 'main__created_at', + query: 'main.created_at', + label: 'Created Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__updated_at', + query: 'main.updated_at', + label: 'Updated Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__name', + query: 'main.name', + label: 'Name', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + ], + filter_configs: [ + { + filed_label: 'Name', + filter_column: 'main__name', + field_type: FILTER_FIELD_TYPE.select, + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + select_data_source_url: '/v1/season-types', + select_custom_options: [], + select_label_key: 'name', + select_value_key: 'name', + }, + { + filed_label: 'Status', + filter_column: 'main__status', + field_type: FILTER_FIELD_TYPE.input_text, + filter_type: FILTER_TYPE.TEXT_EQUAL, + // select_data_source_url: '/v1/season-types', + // select_custom_options: [], + // select_label_key: 'code', + // select_value_key: 'code', + }, + ], +}; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/refunds.ts b/src/modules/reports/shared/configs/transaction-report/configs/refunds.ts new file mode 100644 index 0000000..d274ed0 --- /dev/null +++ b/src/modules/reports/shared/configs/transaction-report/configs/refunds.ts @@ -0,0 +1,156 @@ +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { + DATA_FORMAT, + DATA_TYPE, + FILTER_FIELD_TYPE, + FILTER_TYPE, + REPORT_GROUP, +} from '../../../constant'; +import { ReportConfigEntity } from '../../../entities/report-config.entity'; + +export default { + group_name: REPORT_GROUP.transaction_report, + unique_name: `${REPORT_GROUP.transaction_report}__refunds`, + label: 'Pengembalian', + table_schema: `refunds AS main + LEFT JOIN transactions tr ON tr.id = main.transaction_id`, + main_table_alias: 'main', + defaultOrderBy: [], + lowLevelOrderBy: [], + filter_period_config: { + hidden: true, + }, + + column_configs: [ + { + column: 'main__status', + query: 'main.status', + label: 'Status', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.STATUS, + }, + { + column: 'main__code', + query: 'main.code', + label: 'Kode', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__request_date', + query: 'main.request_date', + label: 'Tgl. Permintaan', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_TIMESTAMP, + date_format: 'DD/MM/YYYY', + }, + { + column: 'main__refund_date', + query: 'main.refund_date', + label: 'Tgl. Refund', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_TIMESTAMP, + date_format: 'DD/MM/YYYY', + }, + { + column: 'main__type', + query: 'main.type', + label: 'Tipe Refund', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'tr__invoice_code', + query: 'tr.invoice_code', + label: 'Kode Settlement', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'tr__settlement_date', + query: 'tr.settlement_date', + label: 'Tgl. Settlement', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + date_format: 'DD/MM/YYYY', + }, + { + column: 'tr__payment_total', + query: 'tr.payment_total', + label: 'Total Pembayaran', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'main__refund_total', + query: 'main.refund_total', + label: 'Total Pengembalian', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'tr__payment_type', + query: 'tr.payment_type', + label: 'Tipe Pembayaran', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'tr__payment_type_method_name', + query: 'tr.payment_type_method_name', + label: 'Bank', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'tr__customer_name', + query: 'tr.customer_name', + label: 'Kontak', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'tr__customer_phone', + query: 'tr.customer_phone', + label: 'Telepon', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'tr__customer_description', + query: 'tr.customer_description', + label: 'Deskripsi', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__creator_name', + query: 'main.creator_name', + label: 'Dibuat Oleh', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__updated_at', + query: 'main.updated_at', + label: 'Tgl. Update', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + ], + filter_configs: [ + { + filed_label: 'Status', + filter_column: 'main__status', + field_type: FILTER_FIELD_TYPE.select, + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + select_custom_options: [ + STATUS.DRAFT, + STATUS.WAITING, + STATUS.PENDING, + STATUS.SETTLED, + STATUS.REJECTED, + ], + }, + ], +}; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/revenue-per-item.ts b/src/modules/reports/shared/configs/transaction-report/configs/revenue-per-item.ts new file mode 100644 index 0000000..9145830 --- /dev/null +++ b/src/modules/reports/shared/configs/transaction-report/configs/revenue-per-item.ts @@ -0,0 +1,40 @@ +import { DATA_FORMAT, DATA_TYPE, REPORT_GROUP } from '../../../constant'; +import { ReportConfigEntity } from '../../../entities/report-config.entity'; + +export default { + group_name: REPORT_GROUP.transaction_report, + unique_name: `${REPORT_GROUP.transaction_report}__revenue_per_item`, + label: 'Pendapatan per Item', + table_schema: 'season_types main', + main_table_alias: 'main', + defaultOrderBy: [], + lowLevelOrderBy: [], + filter_period_config: { + hidden: true, + }, + + column_configs: [ + { + column: 'main__created_at', + query: 'main.created_at', + label: 'Created Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__updated_at', + query: 'main.updated_at', + label: 'Updated Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__name', + query: 'main.name', + label: 'Name', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + ], + filter_configs: [], +}; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/sales-qty-per-item.ts b/src/modules/reports/shared/configs/transaction-report/configs/sales-qty-per-item.ts new file mode 100644 index 0000000..d3a5e1a --- /dev/null +++ b/src/modules/reports/shared/configs/transaction-report/configs/sales-qty-per-item.ts @@ -0,0 +1,40 @@ +import { DATA_FORMAT, DATA_TYPE, REPORT_GROUP } from '../../../constant'; +import { ReportConfigEntity } from '../../../entities/report-config.entity'; + +export default { + group_name: REPORT_GROUP.transaction_report, + unique_name: `${REPORT_GROUP.transaction_report}__sales_qty_per_item`, + label: 'Qty Penjualan per Item', + table_schema: 'season_types main', + main_table_alias: 'main', + defaultOrderBy: [], + lowLevelOrderBy: [], + filter_period_config: { + hidden: true, + }, + + column_configs: [ + { + column: 'main__created_at', + query: 'main.created_at', + label: 'Created Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__updated_at', + query: 'main.updated_at', + label: 'Updated Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__name', + query: 'main.name', + label: 'Name', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + ], + filter_configs: [], +}; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/time-per-ride.ts b/src/modules/reports/shared/configs/transaction-report/configs/time-per-ride.ts new file mode 100644 index 0000000..7910e1c --- /dev/null +++ b/src/modules/reports/shared/configs/transaction-report/configs/time-per-ride.ts @@ -0,0 +1,40 @@ +import { DATA_FORMAT, DATA_TYPE, REPORT_GROUP } from '../../../constant'; +import { ReportConfigEntity } from '../../../entities/report-config.entity'; + +export default { + group_name: REPORT_GROUP.transaction_report, + unique_name: `${REPORT_GROUP.transaction_report}__time_per_ride`, + label: 'Waktu per Wahana', + table_schema: 'season_types main', + main_table_alias: 'main', + defaultOrderBy: [], + lowLevelOrderBy: [], + filter_period_config: { + hidden: true, + }, + + column_configs: [ + { + column: 'main__created_at', + query: 'main.created_at', + label: 'Created Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__updated_at', + query: 'main.updated_at', + label: 'Updated Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__name', + query: 'main.name', + label: 'Name', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + ], + filter_configs: [], +}; diff --git a/src/modules/reports/shared/configs/transaction-report/configs/visitors-per-ride.ts b/src/modules/reports/shared/configs/transaction-report/configs/visitors-per-ride.ts new file mode 100644 index 0000000..f062db7 --- /dev/null +++ b/src/modules/reports/shared/configs/transaction-report/configs/visitors-per-ride.ts @@ -0,0 +1,40 @@ +import { DATA_FORMAT, DATA_TYPE, REPORT_GROUP } from '../../../constant'; +import { ReportConfigEntity } from '../../../entities/report-config.entity'; + +export default { + group_name: REPORT_GROUP.transaction_report, + unique_name: `${REPORT_GROUP.transaction_report}__visitors_per_ride`, + label: 'Pengunjung per Wahana', + table_schema: 'season_types main', + main_table_alias: 'main', + defaultOrderBy: [], + lowLevelOrderBy: [], + filter_period_config: { + hidden: true, + }, + + column_configs: [ + { + column: 'main__created_at', + query: 'main.created_at', + label: 'Created Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__updated_at', + query: 'main.updated_at', + label: 'Updated Date', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_EPOCH, + }, + { + column: 'main__name', + query: 'main.name', + label: 'Name', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + ], + filter_configs: [], +}; diff --git a/src/modules/reports/shared/configs/transaction-report/index.ts b/src/modules/reports/shared/configs/transaction-report/index.ts new file mode 100644 index 0000000..d711420 --- /dev/null +++ b/src/modules/reports/shared/configs/transaction-report/index.ts @@ -0,0 +1,23 @@ +import { ReportConfigEntity } from '../../entities/report-config.entity'; + +import IncomeReport from './configs/income'; +import RevenuePerItemReport from './configs/revenue-per-item'; +import SalesQtyPerItemReport from './configs/sales-qty-per-item'; +import VisitorsPerRideReport from './configs/visitors-per-ride'; +import TimePerRideReport from './configs/time-per-ride'; +import BookingReport from './configs/booking'; +import RefundsReport from './configs/refunds'; +import CashierLogReport from './configs/cashier-log'; +import CashWithdrawalsReport from './configs/cash-withdrawals'; + +export const TransactionReportConfig: ReportConfigEntity[] = [ + // IncomeReport, + // RevenuePerItemReport, + // SalesQtyPerItemReport, + // VisitorsPerRideReport, + // TimePerRideReport, + BookingReport, + RefundsReport, + // CashierLogReport, + // CashWithdrawalsReport, +]; diff --git a/src/modules/reports/shared/constant/index.ts b/src/modules/reports/shared/constant/index.ts index 3b44934..ad8e1a6 100644 --- a/src/modules/reports/shared/constant/index.ts +++ b/src/modules/reports/shared/constant/index.ts @@ -1,3 +1,4 @@ export * from './report-config.constant'; export * from './report-group.constant'; export * from './report-status.constant'; +export * from './report-bookmark-type.constant'; diff --git a/src/modules/reports/shared/constant/report-bookmark-type.constant.ts b/src/modules/reports/shared/constant/report-bookmark-type.constant.ts new file mode 100644 index 0000000..4836f37 --- /dev/null +++ b/src/modules/reports/shared/constant/report-bookmark-type.constant.ts @@ -0,0 +1,4 @@ +export enum REPORT_BOOKMARK_TYPE { + TABLE_CONFIG = 'TABLE_CONFIG', + FILTER_TABLE = 'FILTER_TABLE', +} diff --git a/src/modules/reports/shared/constant/report-group.constant.ts b/src/modules/reports/shared/constant/report-group.constant.ts index 80476fa..e9c1a4f 100644 --- a/src/modules/reports/shared/constant/report-group.constant.ts +++ b/src/modules/reports/shared/constant/report-group.constant.ts @@ -1,5 +1,6 @@ export enum REPORT_GROUP { // PATTERN => MODULE__MENU__SUB_MENU // EXAMPLE: - contact__reports = 'contact__reports', + transaction_report = 'transaction_report', + tenant_report = 'tenant_report', } diff --git a/src/modules/reports/shared/dto/report-bookmark.create.dto.ts b/src/modules/reports/shared/dto/report-bookmark.create.dto.ts new file mode 100644 index 0000000..2279e8b --- /dev/null +++ b/src/modules/reports/shared/dto/report-bookmark.create.dto.ts @@ -0,0 +1,38 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsBoolean, IsObject, IsString, ValidateIf } from 'class-validator'; +import { REPORT_BOOKMARK_TYPE } from '../constant'; + +export class CreateReportBookmarkDto { + @ApiProperty({ name: 'group_name', required: true }) + @IsString() + group_name: string; + + @ApiProperty({ name: 'unique_name', required: true }) + @IsString() + unique_name: string; + + @ApiProperty({ name: 'label', required: true }) + @IsString() + label: string; + + @ApiProperty({ name: 'applied', required: true }) + @IsBoolean() + applied: boolean; + + @ApiProperty({ + name: 'type', + required: true, + default: REPORT_BOOKMARK_TYPE.TABLE_CONFIG, + }) + @IsString() + type: REPORT_BOOKMARK_TYPE; + + @ApiProperty({ + name: 'configuration', + type: Object, + required: true, + }) + @IsObject() + @ValidateIf((body) => body.configuration) + configuration: any; +} diff --git a/src/modules/reports/shared/dto/report-bookmark.get.dto.ts b/src/modules/reports/shared/dto/report-bookmark.get.dto.ts new file mode 100644 index 0000000..655828c --- /dev/null +++ b/src/modules/reports/shared/dto/report-bookmark.get.dto.ts @@ -0,0 +1,43 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { REPORT_BOOKMARK_TYPE } from '../constant'; + +export class GetReportBookmarkDto { + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + group_names?: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + unique_names?: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + types?: REPORT_BOOKMARK_TYPE[]; +} + +export class GetLabelReportBookmarkDto { + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + group_names?: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + unique_names?: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + types?: REPORT_BOOKMARK_TYPE[]; +} diff --git a/src/modules/reports/shared/dto/report-config.get.dto.ts b/src/modules/reports/shared/dto/report-config.get.dto.ts new file mode 100644 index 0000000..2eab64e --- /dev/null +++ b/src/modules/reports/shared/dto/report-config.get.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; + +export class GetReportConfigDto { + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + group_names?: string; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + unique_names?: string[]; +} diff --git a/src/modules/reports/shared/dto/report-data.get.dto.ts b/src/modules/reports/shared/dto/report-data.get.dto.ts new file mode 100644 index 0000000..c848399 --- /dev/null +++ b/src/modules/reports/shared/dto/report-data.get.dto.ts @@ -0,0 +1,42 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsObject, IsString, ValidateIf } from 'class-validator'; +import { QueryModelEntity } from '../entities/query-model.entity'; +import { REPORT_GROUP } from '../constant'; + +export class GetReportDataDto { + @ApiProperty({ + name: 'group_name', + required: true, + default: REPORT_GROUP.transaction_report, + }) + @IsString() + group_name: string; + + @ApiProperty({ + name: 'unique_name', + required: true, + default: `${REPORT_GROUP.transaction_report}__sample`, + }) + @IsString() + unique_name: string; + + @ApiProperty({ + name: 'query_model', + type: Object, + required: true, + default: { + startRow: 0, + endRow: 100, + rowGroupCols: [], + valueCols: [], + pivotCols: [], + pivotMode: true, + groupKeys: [], + filterModel: {}, + sortModel: [], + }, + }) + @IsObject() + @ValidateIf((body) => body.query_model) + query_model: QueryModelEntity; +} diff --git a/src/modules/reports/shared/dto/report-export.create.dto.ts b/src/modules/reports/shared/dto/report-export.create.dto.ts new file mode 100644 index 0000000..087c2e2 --- /dev/null +++ b/src/modules/reports/shared/dto/report-export.create.dto.ts @@ -0,0 +1,29 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { GetReportDataDto } from './report-data.get.dto'; +import { IsString, IsNumber, IsOptional, IsArray } from 'class-validator'; +import { ColumnStateEntity } from '../entities/query-model.entity'; + +export class CreateReportExportDto extends GetReportDataDto { + @ApiProperty({ name: 'time_zone', required: true, default: 'Asia/Jakarta' }) + @IsString() + time_zone: string; + + @ApiProperty({ name: 'file_name', required: false }) + @IsString() + @IsOptional() + file_name?: string; + + @ApiProperty({ + name: 'column_state', + type: [Object], + required: true, + default: [ + { colId: 'main__created_at' }, + { colId: 'main__updated_at' }, + { colId: 'main__name' }, + ], + }) + @IsOptional() + @IsArray() + column_state: ColumnStateEntity[]; +} diff --git a/src/modules/reports/shared/dto/report-export.get.dto.ts b/src/modules/reports/shared/dto/report-export.get.dto.ts new file mode 100644 index 0000000..718dea4 --- /dev/null +++ b/src/modules/reports/shared/dto/report-export.get.dto.ts @@ -0,0 +1,63 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { IsNumber, ValidateIf } from 'class-validator'; + +export class GetReportExportDto { + @ApiProperty({ type: Number, required: false, default: 1 }) + @Transform((body) => Number(body.value)) + @ValidateIf((body) => body.page) + @IsNumber() + page = 1; + + @ApiProperty({ type: Number, required: false, default: 10 }) + @Transform((body) => Number(body.value)) + @ValidateIf((body) => body.limit) + @IsNumber() + limit = 10; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + group_names?: string; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + unique_names?: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + statuses?: string; +} + +export class GetReportExportProcessingDto { + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + group_names?: string; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + unique_names?: string[]; +} + +export class GetReportExportFileNameDto { + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + group_names?: string; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + unique_names?: string[]; +} diff --git a/src/modules/reports/shared/entities/query-model.entity.ts b/src/modules/reports/shared/entities/query-model.entity.ts new file mode 100644 index 0000000..88ff53c --- /dev/null +++ b/src/modules/reports/shared/entities/query-model.entity.ts @@ -0,0 +1,32 @@ +export interface QueryModelEntity { + startRow: number; + endRow: number; + rowGroupCols: RowGroupCol[]; + valueCols: any[]; + pivotCols: any[]; + pivotMode: boolean; + groupKeys: any[]; + filterModel: any; + sortModel: any[]; +} + +interface RowGroupCol { + id: string; + displayName: string; + field: string; +} + +export interface ColumnStateEntity { + colId: string; + width: number; + hide: boolean; + pinned: any; + sort: any; + sortIndex: any; + aggFunc: any; + rowGroup: boolean; + rowGroupIndex: any; + pivot: boolean; + pivotIndex: any; + flex: number; +} diff --git a/src/modules/reports/shared/entities/report-config.entity.ts b/src/modules/reports/shared/entities/report-config.entity.ts new file mode 100644 index 0000000..bca0f5c --- /dev/null +++ b/src/modules/reports/shared/entities/report-config.entity.ts @@ -0,0 +1,59 @@ +import { DATA_FORMAT, DATA_TYPE, FILTER_TYPE } from '../constant'; + +export interface ReportColumnConfigEntity { + column: string; + query: string; + label: string; + type: DATA_TYPE; + format: DATA_FORMAT; + date_format?: string; +} + +export interface FilterConfigEntity { + filter_column: string; + filter_type: FILTER_TYPE; + + filed_label: string; + field_type: string; + hide_field?: boolean; + + select_data_source_url?: string; + select_custom_options?: string[]; + select_value_key?: string; + select_label_key?: string; +} + +export interface FilterPeriodConfigEntity { + key: string; + type: FILTER_TYPE; + note?: string; + hidden?: boolean; +} + +export interface ReportConfigEntity { + group_name: string; + unique_name: string; + label: string; + + table_schema: string; + main_table_alias?: string; + customVirtualTableSchema?( + filterModel: any, + findQueryConfig: (column: string) => string, + createFilterSql: (key: string, item: any) => string, + ): string; + whereCondition?(filterModel: any): string[]; + whereDefaultConditions?: { + column: string; + filter_type: FILTER_TYPE; + values: string[]; + }[]; + defaultOrderBy?: string[]; + lowLevelOrderBy?: string[]; + + column_configs: ReportColumnConfigEntity[]; + filter_configs?: FilterConfigEntity[]; + filter_period_config?: FilterPeriodConfigEntity; + ignore_filter_keys?: string[]; + customQueryColumn?(column: string): string; +} diff --git a/src/modules/reports/shared/helpers/index.ts b/src/modules/reports/shared/helpers/index.ts new file mode 100644 index 0000000..3f9a6a4 --- /dev/null +++ b/src/modules/reports/shared/helpers/index.ts @@ -0,0 +1,3 @@ +export * from './query-builder'; +export * from './rounding-currency'; +export * from './string-formatter'; diff --git a/src/modules/reports/shared/helpers/query-builder.ts b/src/modules/reports/shared/helpers/query-builder.ts new file mode 100644 index 0000000..6c58e96 --- /dev/null +++ b/src/modules/reports/shared/helpers/query-builder.ts @@ -0,0 +1,522 @@ +import { FILTER_TYPE } from '../constant'; +import { QueryModelEntity } from '../entities/query-model.entity'; +import { + ReportColumnConfigEntity, + ReportConfigEntity, +} from '../entities/report-config.entity'; + +export class ReportQueryBuilder { + public reportConfig: ReportConfigEntity; + public queryModel: QueryModelEntity; + + constructor(reportConfig: ReportConfigEntity, queryModel: QueryModelEntity) { + this.reportConfig = reportConfig; + this.queryModel = queryModel; + } + + getBaseConfig() { + const tableSchema = this.reportConfig.table_schema; + const mainTableAlias = this.reportConfig.main_table_alias ?? 'main'; + + const selectSql = this.createSelectSql(); + const selectSqlExport = this.createSelectSqlExport(); + + const whereSql = this.createWhereSql(); + const limitSql = this.createLimitSql(); + + const orderBySql = this.createOrderBySql(); + const orderBySqlExport = this.createOrderBySqlExport(); + + const groupBy = this.createGroupBySql(); + const groupByExport = this.createGroupBySqlExport(); + + return { + tableSchema, + mainTableAlias, + selectSql, + selectSqlExport, + whereSql, + limitSql, + orderBySql, + orderBySqlExport, + ...groupBy, + ...groupByExport, + }; + } + + getSql(): string { + const { + selectSql, + tableSchema, + whereSql, + groupByQuery, + orderBySql, + limitSql, + } = this.getBaseConfig(); + return `SELECT ${selectSql} FROM ${tableSchema} ${whereSql} ${groupByQuery} ${orderBySql} ${limitSql}` as string; + } + + getSqlCount(): string { + const { groupByColumn, mainTableAlias, tableSchema, whereSql } = + this.getBaseConfig(); + + return `SELECT COUNT(${ + groupByColumn ? `DISTINCT ${groupByColumn}` : `${mainTableAlias}.id` + }) ${ + groupByColumn + ? `+ COUNT(DISTINCT CASE WHEN ${groupByColumn} IS NULL THEN 1 END)` + : '' + } AS count FROM ${tableSchema} ${whereSql}` as string; + } + + getSqlExport(): string { + const { + selectSqlExport, + tableSchema, + whereSql, + groupByQueryExport, + orderBySqlExport, + } = this.getBaseConfig(); + return `SELECT ${selectSqlExport} FROM ${tableSchema} ${whereSql} ${groupByQueryExport} ${orderBySqlExport}` as string; + } + + getSqlCountExport(): string { + const { groupByColumnExport, mainTableAlias, tableSchema, whereSql } = + this.getBaseConfig(); + return `SELECT COUNT(${ + groupByColumnExport + ? `DISTINCT ${groupByColumnExport}` + : `${mainTableAlias}.id` + }) ${ + groupByColumnExport + ? `+ COUNT(DISTINCT CASE WHEN ${groupByColumnExport} IS NULL THEN 1 END)` + : '' + } AS count FROM ${tableSchema} ${whereSql}` as string; + } + + isDoingGrouping(rowGroupCols, groupKeys) { + // we are not doing grouping if at the lowest level. we are at the lowest level + // if we are grouping by more columns than we have keys for (that means the user + // has not expanded a lowest level group, OR we are not grouping at all). + return rowGroupCols.length > groupKeys.length; + } + + interpolate(str, o) { + return str.replace(/{([^{}]*)}/g, function (a, b) { + const r = o[b]; + return typeof r === 'string' || typeof r === 'number' ? r : a; + }); + } + + createLimitSql() { + const startRow = this.queryModel.startRow; + const endRow = this.queryModel.endRow; + const pageSize = endRow - startRow; + return ' LIMIT ' + (pageSize + 1) + ' OFFSET ' + startRow; + } + + getRowCount(results) { + if (results === null || results === undefined || results.length === 0) { + return null; + } + const currentLastRow = this.queryModel.startRow + results.length; + return currentLastRow <= this.queryModel.endRow ? currentLastRow : -1; + } + + cutResultsToPageSize(results) { + const pageSize = this.queryModel.endRow - this.queryModel.startRow; + if (results && results.length > pageSize) { + return results.splice(0, pageSize); + } else { + return results; + } + } + + findQueryConfig(column: string) { + const configColumns = this.reportConfig.column_configs ?? []; + + const queryColumn: ReportColumnConfigEntity = configColumns.find( + (el) => el.column === column, + ); + const customQueryColumn = this.reportConfig?.customQueryColumn + ? this.reportConfig.customQueryColumn(column) + : undefined; + + if (customQueryColumn) return customQueryColumn; + else if (queryColumn) return queryColumn.query; + + return column.replace('__', '.'); + } + + // GENERATE SELECT QUERY ============================================ + createSelectSql(): string { + const configColumns = this.reportConfig.column_configs; + const mainTableAlias = this.reportConfig.main_table_alias ?? 'main'; + + const rowGroupCols = this.queryModel.rowGroupCols; + const valueCols = this.queryModel.valueCols; + const groupKeys = this.queryModel.groupKeys; + + if (this.isDoingGrouping(rowGroupCols, groupKeys)) { + const colsToSelect = []; + const rowGroupCol = rowGroupCols[groupKeys.length]; + const rowGroupColChildCount = rowGroupCols[groupKeys.length + 1]; + + colsToSelect.push( + this.findQueryConfig(rowGroupCol.field) + ` AS ${rowGroupCol.field}`, + ); + + if (rowGroupColChildCount) { + colsToSelect.push( + `COUNT(DISTINCT ${this.findQueryConfig( + rowGroupColChildCount.field, + )}) AS count_child_group`, + ); + } else { + colsToSelect.push(`COUNT(${mainTableAlias}.id) AS count_child_group`); + } + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const thisSelf = this; + valueCols.forEach(function (valueCol) { + colsToSelect.push( + `${valueCol.aggFunc} (${thisSelf.findQueryConfig( + valueCol.field, + )}) AS ${valueCol.field}`, + ); + }); + return colsToSelect.join(', '); + } + + const columns = configColumns.map( + (item) => `${item.query} AS ${item.column}`, + ); + + return columns.join(', '); + } + + createSelectSqlExport(): string { + const configColumns = this.reportConfig.column_configs; + + const rowGroupCols = this.queryModel.rowGroupCols; + const valueCols = this.queryModel.valueCols; + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const thisSelf = this; + + if (rowGroupCols.length > 0) { + const colsToSelect = []; + rowGroupCols.forEach(function (rowGroupCol) { + colsToSelect.push( + thisSelf.findQueryConfig(rowGroupCol.field) + + ` AS ${rowGroupCol.field}`, + ); + }); + + valueCols.forEach(function (valueCol) { + colsToSelect.push( + `${valueCol.aggFunc} (${thisSelf.findQueryConfig( + valueCol.field, + )}) AS ${valueCol.field}`, + ); + }); + + return colsToSelect.join(', '); + } + + const columns = configColumns.map((item) => `${item.query} ${item.column}`); + + return columns.join(', '); + } + // ================================================================== + + // GENERATE WHERE QUERY ============================================= + createFilterSql(column: string, item: { type: FILTER_TYPE; filter: any }) { + switch (item.type) { + // TEXT + case FILTER_TYPE.TEXT_EQUAL: + return `${column} = '${item.filter}'`; + + case FILTER_TYPE.TEXT_NOT_EQUAL: + return `${column} != '${item.filter}'`; + + case FILTER_TYPE.TEXT_CONTAINS: + return `${column} ILIKE '%${item.filter}%'`; + + case FILTER_TYPE.TEXT_NOT_CONTAINS: + return `${column} NOT ILIKE '%${item.filter}%'`; + + case FILTER_TYPE.TEXT_START_WITH: + return `${column} ILIKE '${item.filter}%'`; + + case FILTER_TYPE.TEXT_END_WITH: + return `${column} ILIKE '%${item.filter}'`; + + case FILTER_TYPE.TEXT_IN_MEMBER: + return item.filter?.length > 0 + ? `${column} IN(${item.filter.map((i) => `'${i}'`).join(', ')})` + : null; + + case FILTER_TYPE.TEXT_MULTIPLE_CONTAINS: + return item.filter?.length > 0 + ? `${column} ILIKE ANY(ARRAY[${item.filter + .map((i) => `'%${i}%'`) + .join(', ')}])` + : null; + + case FILTER_TYPE.TEXT_MULTIPLE_REGEXP_CONTAINS: + return item.filter?.length > 0 + ? `${column} REGEXP '${item.filter.join('|')}'` + : null; + + case FILTER_TYPE.DATE_IN_RANGE_EPOCH: + return `(${column} >= ${item.filter[0]} AND ${column} <= ${item.filter[1]})`; + + case FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP: + return `(${column} BETWEEN '${item.filter[0]}' AND '${item.filter[1]}')`; + + case FILTER_TYPE.TEXT_IN_RANGE: + return `(${column} >= '${item.filter[0]}' AND ${column} <= '${item.filter[1]}')`; + + // NUMBER + case FILTER_TYPE.NUMBER_EQUAL: + return `${column} = ${item.filter}`; + + case FILTER_TYPE.NUMBER_NOT_EQUAL: + return `${column} != ${item.filter}`; + + case FILTER_TYPE.NUMBER_GREATER_THAN: + return `${column} > ${item.filter}`; + + case FILTER_TYPE.NUMBER_GREATER_THAN_OR_EQUAL: + return `${column} >= ${item.filter}`; + + case FILTER_TYPE.NUMBER_LESS_THAN: + return `${column} < ${item.filter}`; + + case FILTER_TYPE.NUMBER_LESS_THAN_OR_EQUAL: + return `${column} <= ${item.filter}`; + + case FILTER_TYPE.NUMBER_IN_RANGE: + return `(${column} >= ${item.filter[0]} AND ${column} <= ${item.filter[1]})`; + + default: + console.log('UNKNOWN FILTER TYPE:', item.type); + return 'true'; + } + } + + createWhereSql() { + const configFilters = this.reportConfig.filter_configs ?? []; + const ignoreFilterKeys = this.reportConfig.ignore_filter_keys ?? []; + const ignoreFilter = configFilters + .filter((el) => el.hide_field) + .map((el) => el.filter_column); + + const ignoreFilterKey = [...ignoreFilter, ...ignoreFilterKeys]; + + const whereCondition = this.reportConfig?.whereCondition + ? this.reportConfig.whereCondition(this.queryModel.filterModel) + : []; + + const rowGroupCols = this.queryModel.rowGroupCols; + const groupKeys = this.queryModel.groupKeys; + const filterModel = this.queryModel.filterModel; + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const thisSelf = this; + + const whereParts = []; + if (groupKeys.length > 0) { + groupKeys.forEach(function (key, index) { + const colName = rowGroupCols[index].field; + // whereParts.push(colName + ' = "' + key + '"'); + whereParts.push(`${thisSelf.findQueryConfig(colName)} = '${key}'`); + }); + } + + if (filterModel) { + const keySet = Object.keys(filterModel); + keySet.forEach(function (key) { + if (!ignoreFilterKey.includes(key)) { + const item = filterModel[key]; + const newKey = thisSelf.findQueryConfig(key); + whereParts.push(thisSelf.createFilterSql(newKey, item)); + } + }); + } + + // set default where conditions + const defaultConditions = this.reportConfig.whereDefaultConditions; + const defaultWhereOptions = []; + if (defaultConditions) { + defaultConditions.forEach((condition) => { + defaultWhereOptions.push( + this.createFilterSql(condition.column, { + type: condition.filter_type, + filter: condition.values, + }), + ); + }); + } + + const tableWhereConditions = [...whereCondition, ...whereParts].filter( + Boolean, + ); + const defaultWhereConditions = defaultWhereOptions.filter(Boolean); + + if (tableWhereConditions.length > 0) { + return `WHERE (${ + defaultWhereConditions.length + ? defaultWhereConditions?.filter(Boolean).join(' AND ') + ' AND ' + : ' ' + } ${tableWhereConditions.filter(Boolean).join(' AND ')})`; + } else if (defaultWhereConditions.length) { + return `WHERE (${defaultWhereConditions.filter(Boolean).join(' AND ')})`; + } else { + return ''; + } + } + // ================================================================== + + // GENERATE ORDER QUERY ============================================= + createOrderBySql() { + const mainTableAlias = this.reportConfig.main_table_alias ?? 'main'; + const defaultOrderBy = this.reportConfig.defaultOrderBy ?? []; + const lowLevelOrderBy = this.reportConfig.lowLevelOrderBy ?? []; + + const rowGroupCols = this.queryModel.rowGroupCols; + const groupKeys = this.queryModel.groupKeys; + const sortModel = this.queryModel.sortModel; + + const grouping = this.isDoingGrouping(rowGroupCols, groupKeys); + + const sortParts = []; + if (sortModel) { + const groupColIds = rowGroupCols + .map((groupCol) => groupCol.id) + .slice(0, groupKeys.length + 1); + + sortModel.forEach(function (item) { + if (grouping && groupColIds.indexOf(item.colId) < 0) { + // ignore + } else { + sortParts.push(item.colId + ' ' + item.sort); + } + }); + } + + const defaultOrder = defaultOrderBy[0] + ? defaultOrderBy + : [`${mainTableAlias}.created_at DESC`]; + + const lowLevelOrder = lowLevelOrderBy[0] + ? lowLevelOrderBy + : [`${mainTableAlias}.id DESC`]; + + if (sortParts.length > 0) { + if (rowGroupCols?.length > 0) { + if (groupKeys.length > 0) { + const sortBy = sortParts[groupKeys.length]; + if (sortBy) return ' ORDER BY ' + sortBy; + // return ''; + } + + return ' ORDER BY ' + sortParts.join(', '); + } + return ( + ' ORDER BY ' + sortParts.join(', ') + `, ${lowLevelOrder.join(', ')}` + ); + } else { + if (rowGroupCols?.length > 0) { + const sortGroupData = rowGroupCols[groupKeys.length]; + if (sortGroupData) return ' ORDER BY ' + `${sortGroupData['id']} DESC`; + // return ''; + } + + return ( + ` ORDER BY ` + defaultOrder.join(', ') + `, ${lowLevelOrder.join(', ')}` + ); + } + } + + createOrderBySqlExport() { + const mainTableAlias = this.reportConfig.main_table_alias ?? 'main'; + const defaultOrderBy = this.reportConfig.defaultOrderBy ?? []; + const lowLevelOrderBy = this.reportConfig.lowLevelOrderBy ?? []; + + const rowGroupCols = this.queryModel.rowGroupCols; + const sortModel = this.queryModel.sortModel; + + const defaultOrder = defaultOrderBy[0] + ? defaultOrderBy + : [`${mainTableAlias}.created_at DESC`]; + + const lowLevelOrder = lowLevelOrderBy[0] + ? lowLevelOrderBy + : [`${mainTableAlias}.id DESC`]; + + if (sortModel.length > 0) { + const sortParts = sortModel.map((i) => `${i.colId} ${i.sort}`); + return ' ORDER BY ' + `${sortParts.join(', ')}`; + } else { + if (rowGroupCols?.length > 0) { + const sortParts = rowGroupCols.map((i) => `${i.id} DESC`); + return ' ORDER BY ' + `${sortParts.join(', ')}`; + } + + return ( + ` ORDER BY ` + defaultOrder.join(', ') + `, ${lowLevelOrder.join(', ')}` + ); + } + } + // ================================================================== + + // GENERATE GROUP BY QUERY ========================================== + createGroupBySql() { + const rowGroupCols = this.queryModel.rowGroupCols; + const groupKeys = this.queryModel.groupKeys; + + if (this.isDoingGrouping(rowGroupCols, groupKeys)) { + const colsToGroupBy = []; + + const rowGroupCol = rowGroupCols[groupKeys.length]; + colsToGroupBy.push(this.findQueryConfig(rowGroupCol.field)); + + return { + groupByQuery: ' GROUP BY ' + colsToGroupBy.join(', '), + groupByColumn: colsToGroupBy.join(', '), + }; + } else { + return { + groupByQuery: '', + groupByColumn: null, + }; + } + } + + createGroupBySqlExport() { + const rowGroupCols = this.queryModel.rowGroupCols; + + // eslint-disable-next-line @typescript-eslint/no-this-alias + const thisSelf = this; + + if (rowGroupCols.length > 0) { + const colsToGroupBy = []; + rowGroupCols.forEach(function (rowGroupCol) { + colsToGroupBy.push(thisSelf.findQueryConfig(rowGroupCol.field)); + }); + + return { + groupByQueryExport: ' GROUP BY ' + colsToGroupBy.join(', '), + groupByColumnExport: colsToGroupBy.join(', '), + }; + } else { + return { + groupByQueryExport: '', + groupByColumnExport: null, + }; + } + } + // ================================================================== +} diff --git a/src/modules/reports/shared/helpers/rounding-currency.ts b/src/modules/reports/shared/helpers/rounding-currency.ts new file mode 100644 index 0000000..323239b --- /dev/null +++ b/src/modules/reports/shared/helpers/rounding-currency.ts @@ -0,0 +1,4 @@ +export function roundingCurrency(value) { + if (!value) return value; + return Number(value).toFixed(2); +} diff --git a/src/modules/reports/shared/helpers/string-formatter.ts b/src/modules/reports/shared/helpers/string-formatter.ts new file mode 100644 index 0000000..be9f603 --- /dev/null +++ b/src/modules/reports/shared/helpers/string-formatter.ts @@ -0,0 +1,64 @@ +interface CurrencyEntity { + value: number; + currency?: ' IDR' | 'EUR' | 'JPY'; +} +export interface IStringFormatter { + capitalizeFistWord(string: string): string; + capitalizeEachWord(string: string): string; + stringLimiter(string: string, limit: number): string; + upperCase(string: string): string; + lowerCase(string: string): string; + currency(payload: CurrencyEntity): string; +} + +export class BaseStringFormatter implements IStringFormatter { + capitalizeFistWord(string: string): string { + if (typeof string !== 'string') return ''; + return string.charAt(0).toUpperCase() + string.slice(1); + } + + capitalizeEachWord(string: string): string { + if (typeof string !== 'string') return ''; + const newStringSplit = string.split(' '); + const newString = newStringSplit.map((item) => { + return item.charAt(0).toUpperCase() + item.slice(1); + }); + return newString.join(' '); + } + + stringLimiter(string: string, limit: number): string { + if (!limit || !string) return string; + return string.length > limit ? `${string.substring(0, limit)} ...` : string; + } + + upperCase(string: string): string { + if (!string) return ''; + return string.toUpperCase(); + } + lowerCase(string: string): string { + if (!string) return ''; + return string.toLowerCase(); + } + currency(payload: CurrencyEntity): any { + const { value, currency = 'IDR' } = payload; + if (!value) return ''; + return new Intl.NumberFormat('en-IN', { + style: 'currency', + currency, + }).format(value); + } +} + +const stringFormatter = new BaseStringFormatter(); +export const { + capitalizeFistWord, + capitalizeEachWord, + stringLimiter, + upperCase, + lowerCase, + currency, +} = stringFormatter; + +export function transformProductCode(code: string) { + return code?.split('-').join(''); +} diff --git a/src/modules/reports/shared/models/report-bookmark.model.ts b/src/modules/reports/shared/models/report-bookmark.model.ts index 38aa8dc..ffb1c7b 100644 --- a/src/modules/reports/shared/models/report-bookmark.model.ts +++ b/src/modules/reports/shared/models/report-bookmark.model.ts @@ -2,6 +2,7 @@ import { BaseModel } from 'src/core/modules/data/model/base.model'; import { Entity, Column } from 'typeorm'; import { ReportBookmarkEntity } from '../entities/report-bookmark.entity'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { REPORT_BOOKMARK_TYPE } from '../constant'; @Entity(TABLE_NAME.REPORT_BOOKMARK) export class ReportBookmarkModel @@ -22,4 +23,11 @@ export class ReportBookmarkModel @Column('json', { nullable: true }) configuration: any; + + @Column('enum', { + name: 'type', + enum: REPORT_BOOKMARK_TYPE, + default: REPORT_BOOKMARK_TYPE.TABLE_CONFIG, + }) + type: REPORT_BOOKMARK_TYPE; } diff --git a/src/modules/reports/shared/services/base-report.service.ts b/src/modules/reports/shared/services/base-report.service.ts new file mode 100644 index 0000000..bed19b0 --- /dev/null +++ b/src/modules/reports/shared/services/base-report.service.ts @@ -0,0 +1,41 @@ +import { Inject, Injectable } from '@nestjs/common'; +import { UserProvider } from 'src/core/sessions'; +import { BLANK_USER } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class BaseReportService { + @Inject() + protected userProvider: UserProvider; + + getUser() { + try { + return this.userProvider?.user; + } catch (error) { + return BLANK_USER; + } + } + + injectDefaultColumnCreate(payload: PayloadEntity): any { + const currentDate = new Date().getTime(); + const user = this.getUser(); + return { + ...payload, + creator_id: user.id, + creator_name: user.name, + editor_id: user.id, + editor_name: user.name, + created_at: currentDate, + updated_at: currentDate, + }; + } + + injectDefaultColumnUpdate(payload: PayloadEntity) { + const user = this.getUser(); + return { + ...payload, + editor_id: user.id, + editor_name: user.name, + updated_at: new Date().getTime(), + }; + } +} diff --git a/src/modules/season-related/season-period/domain/usecases/managers/create-season-period.manager.ts b/src/modules/season-related/season-period/domain/usecases/managers/create-season-period.manager.ts index 2a53f98..4f10cb0 100644 --- a/src/modules/season-related/season-period/domain/usecases/managers/create-season-period.manager.ts +++ b/src/modules/season-related/season-period/domain/usecases/managers/create-season-period.manager.ts @@ -45,6 +45,7 @@ export class CreateSeasonPeriodManager extends BaseCreateManager { + async prepareData(): Promise { + this.filterParam.limit = 10; + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + Object.assign(this.result, { + data: this.result.data.sort((a, b) => a.priority - b.priority), + }); + return; + } + + get relations(): RelationParam { + return { + // relation only join (for query purpose) + joinRelations: ['season_type'], + + // relation join and select (relasi yang ingin ditampilkan), + selectRelations: [], + + // relation yang hanya ingin dihitung (akan return number) + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.priority`, + `${this.tableName}.created_at`, + `${this.tableName}.creator_name`, + `${this.tableName}.editor_name`, + `${this.tableName}.updated_at`, + `${this.tableName}.status`, + `${this.tableName}.start_date`, + `${this.tableName}.end_date`, + `${this.tableName}.days`, + `${this.tableName}.holiday_name`, + + 'season_type.id', + 'season_type.name', + ]; + } + + get specificFilter(): Param[] { + return [ + { + cols: `${this.tableName}.holiday_name`, + data: this.filterParam.holiday_names, + }, + ]; + } + + setQueryFilter( + queryBuilder: SelectQueryBuilder, + ): SelectQueryBuilder { + queryBuilder.andWhere(`${this.tableName}.start_date <= :date`, { + date: this.filterParam.date ?? new Date().toLocaleDateString(), + }); + queryBuilder.andWhere(`${this.tableName}.end_date >= :date`, { + date: this.filterParam.date ?? new Date().toLocaleDateString(), + }); + + queryBuilder.andWhere(`${this.tableName}.status In (:...statuses)`, { + statuses: [STATUS.ACTIVE], + }); + + return queryBuilder; + } +} diff --git a/src/modules/season-related/season-period/domain/usecases/managers/helpers/validate.helper.ts b/src/modules/season-related/season-period/domain/usecases/managers/helpers/validate.helper.ts index ee74adc..8a8d7ce 100644 --- a/src/modules/season-related/season-period/domain/usecases/managers/helpers/validate.helper.ts +++ b/src/modules/season-related/season-period/domain/usecases/managers/helpers/validate.helper.ts @@ -21,7 +21,7 @@ export async function ValidateSeasonPeriodHelper(dataService, data) { => akan tetapi dapat ditindih oleh season period 2024-08-15, 2024-08-28 days [Sabtu, Senin] (karena ini naik prio menjadi priority 2) */ const query = dataService.getRepository().createQueryBuilder('data'); - let priority: number = 3; + let priority = 3; // libur / specific date if ( data.holidays?.length > 0 || @@ -38,36 +38,12 @@ export async function ValidateSeasonPeriodHelper(dataService, data) { let datas = await query .andWhere('data.priority = :priority', { priority: priority }) - .andWhere( - new Brackets((query) => { - // contoh data tanggal 1 Agustus - 31 Agustus - query.orWhere( - new Brackets((q) => { - return q - .andWhere('data.start_date <= :inputStartDate ', { - inputStartDate: data.start_date, - }) - .andWhere('data.end_date >= :inputEndDate', { - inputEndDate: data.end_date, - }); - }), - ); - - query.orWhere( - new Brackets((q) => { - return q - .andWhere('data.start_date >= :inputStartDate ', { - inputStartDate: data.start_date, - }) - .andWhere('data.end_date <= :inputEndDate', { - inputEndDate: data.end_date, - }); - }), - ); - - return query; - }), - ) + .andWhere(`data.start_date <= :inputStartDate`, { + inputStartDate: data.end_date, + }) + .andWhere(`data.end_date >= :inputEndDate`, { + inputEndDate: data.start_date, + }) .getMany(); if (priority == 2) { diff --git a/src/modules/season-related/season-period/domain/usecases/season-period-data.orchestrator.ts b/src/modules/season-related/season-period/domain/usecases/season-period-data.orchestrator.ts index afedcc4..8db3a3c 100644 --- a/src/modules/season-related/season-period/domain/usecases/season-period-data.orchestrator.ts +++ b/src/modules/season-related/season-period/domain/usecases/season-period-data.orchestrator.ts @@ -60,7 +60,7 @@ export class SeasonPeriodDataOrchestrator extends BaseDataTransactionOrchestrato return this.updatePriceManager.getResult(); } - async delete(dataId): Promise { + async delete(dataId): Promise { this.deleteManager.setData(dataId); this.deleteManager.setService(this.serviceData, TABLE_NAME.SEASON_PERIOD); await this.deleteManager.execute(); @@ -77,7 +77,7 @@ export class SeasonPeriodDataOrchestrator extends BaseDataTransactionOrchestrato return this.batchDeleteManager.getResult(); } - async active(dataId): Promise { + async active(dataId): Promise { this.activeManager.setData(dataId, STATUS.ACTIVE); this.activeManager.setService(this.serviceData, TABLE_NAME.SEASON_PERIOD); await this.activeManager.execute(); @@ -94,7 +94,7 @@ export class SeasonPeriodDataOrchestrator extends BaseDataTransactionOrchestrato return this.batchActiveManager.getResult(); } - async confirm(dataId): Promise { + async confirm(dataId): Promise { this.confirmManager.setData(dataId, STATUS.ACTIVE); this.confirmManager.setService(this.serviceData, TABLE_NAME.SEASON_PERIOD); await this.confirmManager.execute(); @@ -111,7 +111,7 @@ export class SeasonPeriodDataOrchestrator extends BaseDataTransactionOrchestrato return this.batchConfirmManager.getResult(); } - async inactive(dataId): Promise { + async inactive(dataId): Promise { this.inactiveManager.setData(dataId, STATUS.INACTIVE); this.inactiveManager.setService(this.serviceData, TABLE_NAME.SEASON_PERIOD); await this.inactiveManager.execute(); diff --git a/src/modules/season-related/season-period/domain/usecases/season-period-read.orchestrator.ts b/src/modules/season-related/season-period/domain/usecases/season-period-read.orchestrator.ts index 69524cb..e74ba92 100644 --- a/src/modules/season-related/season-period/domain/usecases/season-period-read.orchestrator.ts +++ b/src/modules/season-related/season-period/domain/usecases/season-period-read.orchestrator.ts @@ -8,8 +8,9 @@ import { DetailSeasonPeriodManager } from './managers/detail-season-period.manag import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { IndexSeasonPeriodeItemManager } from './managers/index-season-period-item.manager'; import { ItemRateEntity } from 'src/modules/item-related/item-rate/domain/entities/item-rate.entity'; -import { FilterItemRateDto } from 'src/modules/item-related/item-rate/infrastructure/dto/filter-item-rate.dto'; import { ItemRateReadService } from 'src/modules/item-related/item-rate/data/services/item-rate-read.service'; +import { CurrentSeasonPeriodManager } from './managers/get-current-period.manager'; +import { FilterSeasonPeriodDto } from '../../infrastructure/dto/filter-season-period.dto'; @Injectable() export class SeasonPeriodReadOrchestrator extends BaseReadOrchestrator { @@ -17,6 +18,7 @@ export class SeasonPeriodReadOrchestrator extends BaseReadOrchestrator { + this.currentPeriodManager.setFilterParam(params); + this.currentPeriodManager.setService( + this.serviceData, + TABLE_NAME.SEASON_PERIOD, + ); + await this.currentPeriodManager.execute(); + const data = this.currentPeriodManager.getResult(); + return data.data[0]; + } + async indexItem(params): Promise> { this.indexItemManager.setFilterParam(params); this.indexItemManager.setService( diff --git a/src/modules/season-related/season-period/infrastructure/dto/filter-current-season.dto.ts b/src/modules/season-related/season-period/infrastructure/dto/filter-current-season.dto.ts new file mode 100644 index 0000000..699e4d7 --- /dev/null +++ b/src/modules/season-related/season-period/infrastructure/dto/filter-current-season.dto.ts @@ -0,0 +1,6 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class FilterCurrentSeasonDto { + @ApiProperty({ type: Date, required: false, example: '2024-01-01' }) + date: Date; +} diff --git a/src/modules/season-related/season-period/infrastructure/season-period-data.controller.ts b/src/modules/season-related/season-period/infrastructure/season-period-data.controller.ts index 23f19a5..76c7ed0 100644 --- a/src/modules/season-related/season-period/infrastructure/season-period-data.controller.ts +++ b/src/modules/season-related/season-period/infrastructure/season-period-data.controller.ts @@ -34,7 +34,6 @@ export class SeasonPeriodDataController { @Post('/update-price') async updatePrice(@Body() body: UpdateSeasonPriceDto): Promise { - console.log('here'); return await this.orchestrator.updatePrice(body); } @@ -44,7 +43,7 @@ export class SeasonPeriodDataController { } @Patch(':id/active') - async active(@Param('id') dataId: string): Promise { + async active(@Param('id') dataId: string): Promise { return await this.orchestrator.active(dataId); } @@ -54,7 +53,7 @@ export class SeasonPeriodDataController { } @Patch(':id/confirm') - async confirm(@Param('id') dataId: string): Promise { + async confirm(@Param('id') dataId: string): Promise { return await this.orchestrator.confirm(dataId); } @@ -64,7 +63,7 @@ export class SeasonPeriodDataController { } @Patch(':id/inactive') - async inactive(@Param('id') dataId: string): Promise { + async inactive(@Param('id') dataId: string): Promise { return await this.orchestrator.inactive(dataId); } @@ -91,7 +90,7 @@ export class SeasonPeriodDataController { } @Delete(':id') - async delete(@Param('id') dataId: string): Promise { + async delete(@Param('id') dataId: string): Promise { return await this.orchestrator.delete(dataId); } } diff --git a/src/modules/season-related/season-period/infrastructure/season-period-read.controller.ts b/src/modules/season-related/season-period/infrastructure/season-period-read.controller.ts index 3c9a3a8..463a35f 100644 --- a/src/modules/season-related/season-period/infrastructure/season-period-read.controller.ts +++ b/src/modules/season-related/season-period/infrastructure/season-period-read.controller.ts @@ -9,6 +9,7 @@ import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; import { Public } from 'src/core/guards'; import { ItemRateEntity } from 'src/modules/item-related/item-rate/domain/entities/item-rate.entity'; import { FilterItemRateDto } from 'src/modules/item-related/item-rate/infrastructure/dto/filter-item-rate.dto'; +import { FilterCurrentSeasonDto } from './dto/filter-current-season.dto'; @ApiTags(`${MODULE_NAME.SEASON_PERIOD.split('-').join(' ')} - read`) @Controller(`v1/${MODULE_NAME.SEASON_PERIOD}`) @@ -25,6 +26,13 @@ export class SeasonPeriodReadController { return await this.orchestrator.index(params); } + @Get('current-period') + async currentPeriod( + @Query() params: FilterCurrentSeasonDto, + ): Promise { + return await this.orchestrator.currentPeriod(params); + } + @Get(':id') async detail(@Param('id') id: string): Promise { return await this.orchestrator.detail(id); diff --git a/src/modules/season-related/season-period/season-period.module.ts b/src/modules/season-related/season-period/season-period.module.ts index 7351a78..6adce88 100644 --- a/src/modules/season-related/season-period/season-period.module.ts +++ b/src/modules/season-related/season-period/season-period.module.ts @@ -28,6 +28,7 @@ import { ItemRateModel } from 'src/modules/item-related/item-rate/data/models/it import { ItemRateReadService } from 'src/modules/item-related/item-rate/data/services/item-rate-read.service'; import { SeasonPeriodPriceUpdatedHandler } from './domain/usecases/handlers/season-period-price-updated.handler'; import { UpdateSeasonPeriodPriceManager } from './domain/usecases/managers/update-season-period-price.manager'; +import { CurrentSeasonPeriodManager } from './domain/usecases/managers/get-current-period.manager'; @Module({ imports: [ @@ -57,6 +58,7 @@ import { UpdateSeasonPeriodPriceManager } from './domain/usecases/managers/updat BatchActiveSeasonPeriodManager, BatchConfirmSeasonPeriodManager, BatchInactiveSeasonPeriodManager, + CurrentSeasonPeriodManager, SeasonPeriodDataService, SeasonPeriodReadService, diff --git a/src/modules/season-related/season-type/domain/entities/filter-season-type.entity.ts b/src/modules/season-related/season-type/domain/entities/filter-season-type.entity.ts index bbdfd80..8ceaa32 100644 --- a/src/modules/season-related/season-type/domain/entities/filter-season-type.entity.ts +++ b/src/modules/season-related/season-type/domain/entities/filter-season-type.entity.ts @@ -1,3 +1,3 @@ import { BaseFilterEntity } from 'src/core/modules/domain/entities/base-filter.entity'; -export interface FilterSeasonTypeEntity extends BaseFilterEntity {} +export type FilterSeasonTypeEntity = BaseFilterEntity; diff --git a/src/modules/season-related/season-type/domain/usecases/season-type-data.orchestrator.ts b/src/modules/season-related/season-type/domain/usecases/season-type-data.orchestrator.ts index dd5a032..52a07fa 100644 --- a/src/modules/season-related/season-type/domain/usecases/season-type-data.orchestrator.ts +++ b/src/modules/season-related/season-type/domain/usecases/season-type-data.orchestrator.ts @@ -48,7 +48,7 @@ export class SeasonTypeDataOrchestrator extends BaseDataTransactionOrchestrator< return this.updateManager.getResult(); } - async delete(dataId): Promise { + async delete(dataId): Promise { this.deleteManager.setData(dataId); this.deleteManager.setService(this.serviceData, TABLE_NAME.SEASON_TYPE); await this.deleteManager.execute(); @@ -65,7 +65,7 @@ export class SeasonTypeDataOrchestrator extends BaseDataTransactionOrchestrator< return this.batchDeleteManager.getResult(); } - async active(dataId): Promise { + async active(dataId): Promise { this.activeManager.setData(dataId, STATUS.ACTIVE); this.activeManager.setService(this.serviceData, TABLE_NAME.SEASON_TYPE); await this.activeManager.execute(); @@ -82,7 +82,7 @@ export class SeasonTypeDataOrchestrator extends BaseDataTransactionOrchestrator< return this.batchActiveManager.getResult(); } - async confirm(dataId): Promise { + async confirm(dataId): Promise { this.confirmManager.setData(dataId, STATUS.ACTIVE); this.confirmManager.setService(this.serviceData, TABLE_NAME.SEASON_TYPE); await this.confirmManager.execute(); @@ -99,7 +99,7 @@ export class SeasonTypeDataOrchestrator extends BaseDataTransactionOrchestrator< return this.batchConfirmManager.getResult(); } - async inactive(dataId): Promise { + async inactive(dataId): Promise { this.inactiveManager.setData(dataId, STATUS.INACTIVE); this.inactiveManager.setService(this.serviceData, TABLE_NAME.SEASON_TYPE); await this.inactiveManager.execute(); diff --git a/src/modules/season-related/season-type/infrastructure/season-type-data.controller.ts b/src/modules/season-related/season-type/infrastructure/season-type-data.controller.ts index 60230da..cda08ba 100644 --- a/src/modules/season-related/season-type/infrastructure/season-type-data.controller.ts +++ b/src/modules/season-related/season-type/infrastructure/season-type-data.controller.ts @@ -34,7 +34,7 @@ export class SeasonTypeDataController { } @Patch(':id/active') - async active(@Param('id') dataId: string): Promise { + async active(@Param('id') dataId: string): Promise { return await this.orchestrator.active(dataId); } @@ -44,7 +44,7 @@ export class SeasonTypeDataController { } @Patch(':id/confirm') - async confirm(@Param('id') dataId: string): Promise { + async confirm(@Param('id') dataId: string): Promise { return await this.orchestrator.confirm(dataId); } @@ -54,7 +54,7 @@ export class SeasonTypeDataController { } @Patch(':id/inactive') - async inactive(@Param('id') dataId: string): Promise { + async inactive(@Param('id') dataId: string): Promise { return await this.orchestrator.inactive(dataId); } @@ -72,7 +72,7 @@ export class SeasonTypeDataController { } @Delete(':id') - async delete(@Param('id') dataId: string): Promise { + async delete(@Param('id') dataId: string): Promise { return await this.orchestrator.delete(dataId); } } diff --git a/src/modules/transaction/payment-method/domain/usecases/payment-method-data.orchestrator.ts b/src/modules/transaction/payment-method/domain/usecases/payment-method-data.orchestrator.ts index bd132c1..fe3454d 100644 --- a/src/modules/transaction/payment-method/domain/usecases/payment-method-data.orchestrator.ts +++ b/src/modules/transaction/payment-method/domain/usecases/payment-method-data.orchestrator.ts @@ -48,7 +48,7 @@ export class PaymentMethodDataOrchestrator extends BaseDataTransactionOrchestrat return this.updateManager.getResult(); } - async delete(dataId): Promise { + async delete(dataId): Promise { this.deleteManager.setData(dataId); this.deleteManager.setService(this.serviceData, TABLE_NAME.PAYMENT_METHOD); await this.deleteManager.execute(); @@ -65,7 +65,7 @@ export class PaymentMethodDataOrchestrator extends BaseDataTransactionOrchestrat return this.batchDeleteManager.getResult(); } - async active(dataId): Promise { + async active(dataId): Promise { this.activeManager.setData(dataId, STATUS.ACTIVE); this.activeManager.setService(this.serviceData, TABLE_NAME.PAYMENT_METHOD); await this.activeManager.execute(); @@ -82,7 +82,7 @@ export class PaymentMethodDataOrchestrator extends BaseDataTransactionOrchestrat return this.batchActiveManager.getResult(); } - async confirm(dataId): Promise { + async confirm(dataId): Promise { this.confirmManager.setData(dataId, STATUS.ACTIVE); this.confirmManager.setService(this.serviceData, TABLE_NAME.PAYMENT_METHOD); await this.confirmManager.execute(); @@ -99,7 +99,7 @@ export class PaymentMethodDataOrchestrator extends BaseDataTransactionOrchestrat return this.batchConfirmManager.getResult(); } - async inactive(dataId): Promise { + async inactive(dataId): Promise { this.inactiveManager.setData(dataId, STATUS.INACTIVE); this.inactiveManager.setService( this.serviceData, diff --git a/src/modules/transaction/payment-method/infrastructure/payment-method-data.controller.ts b/src/modules/transaction/payment-method/infrastructure/payment-method-data.controller.ts index ec50991..c708c2d 100644 --- a/src/modules/transaction/payment-method/infrastructure/payment-method-data.controller.ts +++ b/src/modules/transaction/payment-method/infrastructure/payment-method-data.controller.ts @@ -34,7 +34,7 @@ export class PaymentMethodDataController { } @Patch(':id/active') - async active(@Param('id') dataId: string): Promise { + async active(@Param('id') dataId: string): Promise { return await this.orchestrator.active(dataId); } @@ -44,7 +44,7 @@ export class PaymentMethodDataController { } @Patch(':id/confirm') - async confirm(@Param('id') dataId: string): Promise { + async confirm(@Param('id') dataId: string): Promise { return await this.orchestrator.confirm(dataId); } @@ -54,7 +54,7 @@ export class PaymentMethodDataController { } @Patch(':id/inactive') - async inactive(@Param('id') dataId: string): Promise { + async inactive(@Param('id') dataId: string): Promise { return await this.orchestrator.inactive(dataId); } @@ -72,7 +72,7 @@ export class PaymentMethodDataController { } @Delete(':id') - async delete(@Param('id') dataId: string): Promise { + async delete(@Param('id') dataId: string): Promise { return await this.orchestrator.delete(dataId); } } diff --git a/src/modules/transaction/profit-share-formula/domain/usecases/managers/update-profit-share-formula.manager.ts b/src/modules/transaction/profit-share-formula/domain/usecases/managers/update-profit-share-formula.manager.ts index 59139db..f9151e1 100644 --- a/src/modules/transaction/profit-share-formula/domain/usecases/managers/update-profit-share-formula.manager.ts +++ b/src/modules/transaction/profit-share-formula/domain/usecases/managers/update-profit-share-formula.manager.ts @@ -8,10 +8,20 @@ import { import { SalesPriceFormulaModel } from 'src/modules/transaction/sales-price-formula/data/models/sales-price-formula.model'; import { ProfitShareFormulaUpdatedEvent } from '../../entities/event/profit-share-formula-updated.event'; import { SalesPriceFormulaEntity } from 'src/modules/transaction/sales-price-formula/domain/entities/sales-price-formula.entity'; +import { In } from 'typeorm'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { calculateProfitFormula } from 'src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper'; @Injectable() export class UpdateProfitShareFormulaManager extends BaseUpdateManager { async validateProcess(): Promise { + const taxes = await this.dataServiceFirstOpt.getManyByOptions({ + where: { + status: In([STATUS.ACTIVE]), + }, + }); + + calculateProfitFormula(this.data.formula_string, taxes, 10000, 50, true); return; } diff --git a/src/modules/transaction/profit-share-formula/domain/usecases/profit-share-formula-data.orchestrator.ts b/src/modules/transaction/profit-share-formula/domain/usecases/profit-share-formula-data.orchestrator.ts index 2b3806f..a1d68df 100644 --- a/src/modules/transaction/profit-share-formula/domain/usecases/profit-share-formula-data.orchestrator.ts +++ b/src/modules/transaction/profit-share-formula/domain/usecases/profit-share-formula-data.orchestrator.ts @@ -4,12 +4,14 @@ import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { SalesPriceFormulaDataService } from 'src/modules/transaction/sales-price-formula/data/services/sales-price-formula-data.service'; import { FormulaType } from 'src/modules/transaction/sales-price-formula/constants'; import { SalesPriceFormulaEntity } from 'src/modules/transaction/sales-price-formula/domain/entities/sales-price-formula.entity'; +import { TaxDataService } from 'src/modules/transaction/tax/data/services/tax-data.service'; @Injectable() export class ProfitShareFormulaDataOrchestrator { constructor( private updateManager: UpdateProfitShareFormulaManager, private serviceData: SalesPriceFormulaDataService, + private taxService: TaxDataService, ) {} async update(data): Promise { @@ -20,7 +22,11 @@ export class ProfitShareFormulaDataOrchestrator { }); this.updateManager.setData(formula.id, data); - this.updateManager.setService(this.serviceData, TABLE_NAME.PRICE_FORMULA); + this.updateManager.setService( + this.serviceData, + TABLE_NAME.PRICE_FORMULA, + this.taxService, + ); await this.updateManager.execute(); return this.updateManager.getResult(); } diff --git a/src/modules/transaction/profit-share-formula/profit-share-formula.module.ts b/src/modules/transaction/profit-share-formula/profit-share-formula.module.ts index ea4105b..31ff6f2 100644 --- a/src/modules/transaction/profit-share-formula/profit-share-formula.module.ts +++ b/src/modules/transaction/profit-share-formula/profit-share-formula.module.ts @@ -10,11 +10,16 @@ import { CqrsModule } from '@nestjs/cqrs'; import { UpdateProfitShareFormulaManager } from './domain/usecases/managers/update-profit-share-formula.manager'; import { DetailProfitShareFormulaManager } from './domain/usecases/managers/detail-profit-share-formula.manager'; import { SalesPriceFormulaModel } from '../sales-price-formula/data/models/sales-price-formula.model'; +import { TaxDataService } from '../tax/data/services/tax-data.service'; +import { TaxModel } from '../tax/data/models/tax.model'; @Module({ imports: [ ConfigModule.forRoot(), - TypeOrmModule.forFeature([SalesPriceFormulaModel], CONNECTION_NAME.DEFAULT), + TypeOrmModule.forFeature( + [SalesPriceFormulaModel, TaxModel], + CONNECTION_NAME.DEFAULT, + ), CqrsModule, ], controllers: [ @@ -27,6 +32,8 @@ import { SalesPriceFormulaModel } from '../sales-price-formula/data/models/sales ProfitShareFormulaDataOrchestrator, ProfitShareFormulaReadOrchestrator, + + TaxDataService, ], }) export class ProfitShareFormulaModule {} diff --git a/src/modules/transaction/reconciliation/constants.ts b/src/modules/transaction/reconciliation/constants.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/transaction/reconciliation/domain/usecases/handlers/recap-pos-transaction.handler.ts b/src/modules/transaction/reconciliation/domain/usecases/handlers/recap-pos-transaction.handler.ts new file mode 100644 index 0000000..04d2cab --- /dev/null +++ b/src/modules/transaction/reconciliation/domain/usecases/handlers/recap-pos-transaction.handler.ts @@ -0,0 +1,24 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { CronMidnightEvent } from 'src/modules/configuration/cron/domain/entities/cron-midnight.event'; +import { RecapReconciliationManager } from '../managers/recap-reconciliation.manager'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; + +@EventsHandler(CronMidnightEvent) +export class RecapPosTransactionHandler + implements IEventHandler +{ + constructor( + private recapManager: RecapReconciliationManager, + private dataService: TransactionDataService, + ) {} + + async handle(event: CronMidnightEvent) { + const data = new TransactionModel(); + this.recapManager.setData(data); + this.recapManager.setService(this.dataService, TABLE_NAME.TRANSACTION); + await this.recapManager.execute(); + return this.recapManager.getResult(); + } +} diff --git a/src/modules/transaction/reconciliation/domain/usecases/managers/batch-cancel-reconciliation.manager.ts b/src/modules/transaction/reconciliation/domain/usecases/managers/batch-cancel-reconciliation.manager.ts new file mode 100644 index 0000000..5db3089 --- /dev/null +++ b/src/modules/transaction/reconciliation/domain/usecases/managers/batch-cancel-reconciliation.manager.ts @@ -0,0 +1,76 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class BatchCancelReconciliationManager extends BaseBatchUpdateStatusManager { + async validateData(data: TransactionEntity): Promise { + const transaction = await this.dataService.getOneByOptions({ + where: { + id: data.id, + }, + }); + + if (transaction.status != STATUS.SETTLED) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! cant cancel transaction not settled`, + error: 'Unprocessable Entity', + }); + } + + Object.assign(data, { + reconciliation_mdr: this.data.reconciliation_mdr ?? null, + reconciliation_confirm_by: this.user.name, + reconciliation_confirm_date: new Date().getTime(), + status: this.dataStatus, + reconciliation_status: this.dataStatus, + payment_date: this.data.payment_date, + }); + + if (data.is_recap_transaction) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! cant cancel recap data`, + error: 'Unprocessable Entity', + }); + } + + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return []; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/transaction/reconciliation/domain/usecases/managers/batch-confirm-reconciliation.manager.ts b/src/modules/transaction/reconciliation/domain/usecases/managers/batch-confirm-reconciliation.manager.ts new file mode 100644 index 0000000..4adec65 --- /dev/null +++ b/src/modules/transaction/reconciliation/domain/usecases/managers/batch-confirm-reconciliation.manager.ts @@ -0,0 +1,54 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; +import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class BatchConfirmReconciliationManager extends BaseBatchUpdateStatusManager { + async validateData(data: TransactionEntity): Promise { + const net_profit = data.reconciliation_mdr + ? Number(this.data.payment_total) - Number(this.data.reconciliation_mdr) + : null; + + Object.assign(data, { + reconciliation_mdr: this.data.reconciliation_mdr ?? null, + reconciliation_confirm_by: this.user.name, + reconciliation_confirm_date: new Date().getTime(), + status: STATUS.SETTLED, + reconciliation_status: this.dataStatus, + payment_total_net_profit: net_profit, + payment_date: this.data.payment_date, + }); + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return []; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/transaction/reconciliation/domain/usecases/managers/cancel-reconciliation.manager.ts b/src/modules/transaction/reconciliation/domain/usecases/managers/cancel-reconciliation.manager.ts new file mode 100644 index 0000000..fb48668 --- /dev/null +++ b/src/modules/transaction/reconciliation/domain/usecases/managers/cancel-reconciliation.manager.ts @@ -0,0 +1,73 @@ +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; + +@Injectable() +export class CancelReconciliationManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.id}`; + } + + async validateProcess(): Promise { + // untuk dapat current status + const transaction = await this.dataService.getOneByOptions({ + where: { + id: this.dataId, + }, + }); + + if (transaction.status != STATUS.SETTLED) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! cant cancel transaction not settled`, + error: 'Unprocessable Entity', + }); + } else if (this.data.is_recap_transaction) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! cant cancel recap data`, + error: 'Unprocessable Entity', + }); + } + return; + } + + async beforeProcess(): Promise { + Object.assign(this.data, { + reconciliation_mdr: this.data.reconciliation_mdr ?? null, + reconciliation_confirm_by: this.user.name, + reconciliation_confirm_date: new Date().getTime(), + status: this.dataStatus, + reconciliation_status: this.dataStatus, + payment_date: this.data.payment_date, + }); + + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return []; + } +} diff --git a/src/modules/transaction/reconciliation/domain/usecases/managers/confirm-reconciliation.manager.ts b/src/modules/transaction/reconciliation/domain/usecases/managers/confirm-reconciliation.manager.ts new file mode 100644 index 0000000..a0885a0 --- /dev/null +++ b/src/modules/transaction/reconciliation/domain/usecases/managers/confirm-reconciliation.manager.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@nestjs/common'; +import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; + +@Injectable() +export class ConfirmReconciliationManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.id}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + Object.assign(this.data, { + reconciliation_confirm_by: this.user.name, + reconciliation_confirm_date: new Date().getTime(), + status: STATUS.SETTLED, + reconciliation_status: this.dataStatus, + }); + + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return []; + } +} diff --git a/src/modules/transaction/reconciliation/domain/usecases/managers/detail-reconciliation.manager.ts b/src/modules/transaction/reconciliation/domain/usecases/managers/detail-reconciliation.manager.ts new file mode 100644 index 0000000..2598647 --- /dev/null +++ b/src/modules/transaction/reconciliation/domain/usecases/managers/detail-reconciliation.manager.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDetailManager } from 'src/core/modules/domain/usecase/managers/base-detail.manager'; +import { RelationParam } from 'src/core/modules/domain/entities/base-filter.entity'; +import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; + +@Injectable() +export class DetailReconciliationManager extends BaseDetailManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + // relation only join (for query purpose) + joinRelations: [], + + // relation join and select (relasi yang ingin ditampilkan), + selectRelations: [], + + // relation yang hanya ingin dihitung (akan return number) + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.reconciliation_mdr`, + + `${this.tableName}.payment_type`, + `${this.tableName}.payment_type_method_id`, + `${this.tableName}.payment_type_method_name`, + `${this.tableName}.payment_type_method_number`, + `${this.tableName}.payment_code_reference`, + `${this.tableName}.payment_date`, + + `${this.tableName}.payment_total`, + `${this.tableName}.payment_total_net_profit`, + ]; + } + + get setFindProperties(): any { + return { + id: this.dataId, + }; + } +} diff --git a/src/modules/transaction/reconciliation/domain/usecases/managers/index-reconciliation.manager.ts b/src/modules/transaction/reconciliation/domain/usecases/managers/index-reconciliation.manager.ts new file mode 100644 index 0000000..6eba3a6 --- /dev/null +++ b/src/modules/transaction/reconciliation/domain/usecases/managers/index-reconciliation.manager.ts @@ -0,0 +1,152 @@ +import { Injectable } from '@nestjs/common'; +import { BaseIndexManager } from 'src/core/modules/domain/usecase/managers/base-index.manager'; +import { SelectQueryBuilder } from 'typeorm'; +import { + Param, + RelationParam, +} from 'src/core/modules/domain/entities/base-filter.entity'; +import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; +import { BetweenQueryHelper } from 'src/core/helpers/query/between-query.helper'; + +@Injectable() +export class IndexReconciliationManager extends BaseIndexManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + // relation only join (for query purpose) + joinRelations: [], + + // relation join and select (relasi yang ingin ditampilkan), + selectRelations: [], + + // relation yang hanya ingin dihitung (akan return number) + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.type`, + `${this.tableName}.reconciliation_status`, + `${this.tableName}.reconciliation_mdr`, + `${this.tableName}.reconciliation_confirm_date`, + `${this.tableName}.reconciliation_confirm_by`, + + `${this.tableName}.customer_name`, + `${this.tableName}.creator_counter_no`, + + `${this.tableName}.payment_type`, + `${this.tableName}.payment_type_method_id`, + `${this.tableName}.payment_type_method_name`, + `${this.tableName}.payment_type_method_number`, + `${this.tableName}.payment_code_reference`, + `${this.tableName}.payment_date`, + + `${this.tableName}.payment_total`, + `${this.tableName}.payment_total_net_profit`, + ]; + } + + get specificFilter(): Param[] { + return [ + { + cols: `${this.tableName}.customer_name`, + data: this.filterParam.customer_names, + }, + { + cols: `${this.tableName}.reconciliation_confirm_by`, + data: this.filterParam.confirm_by_names, + }, + { + cols: `${this.tableName}.payment_type_method_name`, + data: this.filterParam.payment_banks, + }, + { + cols: `${this.tableName}.payment_type_method_number`, + data: this.filterParam.payment_bank_numbers, + }, + ]; + } + + setQueryFilter( + queryBuilder: SelectQueryBuilder, + ): SelectQueryBuilder { + if (this.filterParam.payment_date_from) { + new BetweenQueryHelper( + queryBuilder, + this.tableName, + 'payment_date', + this.filterParam.payment_date_from, + this.filterParam.payment_date_to, + 'payment_created', + ).getQuery(); + } + + if (this.filterParam.transaction_type) { + queryBuilder.andWhere(`${this.tableName}.type In (:...types)`, { + types: [this.filterParam.transaction_type], + }); + } + + if (this.filterParam.couner_no) { + queryBuilder.andWhere( + `${this.tableName}.creator_counter_no In (:...counters)`, + { + counters: [this.filterParam.couner_no], + }, + ); + } + + if (this.filterParam.payment_type) { + queryBuilder.andWhere( + `${this.tableName}.creator_counter_no In (:...counters)`, + { + counters: [this.filterParam.couner_no], + }, + ); + } + + if (this.filterParam.payment_via) { + queryBuilder.andWhere(`${this.tableName}.payment_type In (:...type)`, { + type: [this.filterParam.payment_via], + }); + } + + if (this.filterParam.payment_bank) { + queryBuilder.andWhere( + `${this.tableName}.payment_type_method_name In (:...banks)`, + { + banks: [this.filterParam.payment_bank], + }, + ); + } + + if (this.filterParam.confirmation_date_from) { + new BetweenQueryHelper( + queryBuilder, + this.tableName, + 'payment_date', + this.filterParam.confirmation_date_from, + this.filterParam.confirmation_date_to, + 'payment_created', + ).getQuery(); + } + + queryBuilder.andWhere( + `${this.tableName}.reconciliation_status Is Not Null`, + ); + return queryBuilder; + } +} diff --git a/src/modules/transaction/reconciliation/domain/usecases/managers/recap-reconciliation.manager.ts b/src/modules/transaction/reconciliation/domain/usecases/managers/recap-reconciliation.manager.ts new file mode 100644 index 0000000..25d25f3 --- /dev/null +++ b/src/modules/transaction/reconciliation/domain/usecases/managers/recap-reconciliation.manager.ts @@ -0,0 +1,138 @@ +import { Injectable } from '@nestjs/common'; +import { BaseCustomManager } from 'src/core/modules/domain/usecase/managers/base-custom.manager'; +import { EventTopics } from 'src/core/strings/constants/interface.constants'; +import { TransactionType } from 'src/modules/transaction/transaction/constants'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; +import { Between } from 'typeorm'; +import * as _ from 'lodash'; +import * as moment from 'moment'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class RecapReconciliationManager extends BaseCustomManager { + private recapTransactions = {}; + private startOfDay = moment().startOf('day').unix(); + private endOfDay = moment().endOf('day').unix(); + + get entityTarget(): any { + return TransactionModel; + } + + getResult() { + return; + } + + get eventTopics(): EventTopics[] { + return []; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + const transactions = await this.dataService.getManyByOptions({ + where: { + is_recap_transaction: false, + type: TransactionType.COUNTER, + created_at: Between(this.startOfDay, this.endOfDay), + }, + }); + + for (const transaction of transactions) { + const { + creator_counter_no, + payment_type_method_name, + payment_type_method_number, + } = transaction; + const group_by = + creator_counter_no + + '-' + + payment_type_method_name + + '-' + + payment_type_method_number; + if (!this.recapTransactions[group_by]) { + this.recapTransactions[group_by] = []; + } + this.recapTransactions[group_by].push(transaction); + } + + return; + } + + async process(): Promise { + const total_recap = Object.keys(this.recapTransactions); + for (const recap of total_recap) { + const first_transaction = this.recapTransactions[recap][0]; + const { + creator_counter_no, + payment_type_method_number, + payment_type_method_name, + } = first_transaction; + + const exist = await this.dataService.getOneByOptions({ + where: { + is_recap_transaction: true, + created_at: Between(this.startOfDay, this.endOfDay), + creator_counter_no: creator_counter_no, + payment_type_method_number: payment_type_method_number, + payment_type_method_name: payment_type_method_name, + }, + }); + const new_recap = new TransactionModel(); + + if (exist) { + Object.assign(exist, { + payment_total: _.sumBy(this.recapTransactions[recap], (recap) => + parseFloat(recap.payment_total), + ), + payment_total_net_profit: _.sumBy( + this.recapTransactions[recap], + (recap) => parseFloat(recap.payment_total), + ), + editor_id: this.user.id, + editor_name: this.user.name, + updated_at: new Date().getTime(), + }); + } else { + Object.assign(new_recap, { + is_recap_transaction: true, + payment_total: _.sumBy(this.recapTransactions[recap], (recap) => + parseFloat(recap.payment_total), + ), + payment_total_net_profit: _.sumBy( + this.recapTransactions[recap], + (recap) => parseFloat(recap.payment_total), + ), + reconciliation_status: STATUS.PENDING, + status: STATUS.SETTLED, + type: TransactionType.COUNTER, + booking_date: first_transaction.booking_date, + creator_counter_no: first_transaction.creator_counter_no, + payment_type: first_transaction.payment_type, + payment_type_method_id: first_transaction.payment_type_method_id, + payment_type_method_number: + first_transaction.payment_type_method_number, + payment_type_method_name: first_transaction.payment_type_method_name, + payment_type_method_qr: first_transaction.payment_type_method_qr, + creator_id: this.user.id, + creator_name: this.user.name, + created_at: new Date().getTime(), + updated_at: new Date().getTime(), + }); + } + + await this.dataService.create( + this.queryRunner, + TransactionModel, + exist ?? new_recap, + ); + } + return; + } + + async afterProcess(): Promise { + return; + } +} diff --git a/src/modules/transaction/reconciliation/domain/usecases/managers/update-reconciliation.manager.ts b/src/modules/transaction/reconciliation/domain/usecases/managers/update-reconciliation.manager.ts new file mode 100644 index 0000000..9a52e45 --- /dev/null +++ b/src/modules/transaction/reconciliation/domain/usecases/managers/update-reconciliation.manager.ts @@ -0,0 +1,51 @@ +import { Injectable } from '@nestjs/common'; +import { BaseUpdateManager } from 'src/core/modules/domain/usecase/managers/base-update.manager'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; + +@Injectable() +export class UpdateReconciliationManager extends BaseUpdateManager { + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + const net_profit = this.data.reconciliation_mdr + ? Number(this.oldData.payment_total) - + Number(this.data.reconciliation_mdr) + : null; + + Object.assign(this.data, { + reconciliation_mdr: this.data.reconciliation_mdr ?? null, + payment_total_net_profit: net_profit, + payment_date: this.data.payment_date ?? this.oldData.payment_date, + }); + + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return []; + } +} diff --git a/src/modules/transaction/reconciliation/domain/usecases/reconciliation-data.orchestrator.ts b/src/modules/transaction/reconciliation/domain/usecases/reconciliation-data.orchestrator.ts new file mode 100644 index 0000000..62b229d --- /dev/null +++ b/src/modules/transaction/reconciliation/domain/usecases/reconciliation-data.orchestrator.ts @@ -0,0 +1,75 @@ +import { Injectable } from '@nestjs/common'; +import { UpdateReconciliationManager } from './managers/update-reconciliation.manager'; +import { ConfirmReconciliationManager } from './managers/confirm-reconciliation.manager'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchConfirmReconciliationManager } from './managers/batch-confirm-reconciliation.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { CancelReconciliationManager } from './managers/cancel-reconciliation.manager'; +import { BatchCancelReconciliationManager } from './managers/batch-cancel-reconciliation.manager'; +import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; +import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; +import { RecapReconciliationManager } from './managers/recap-reconciliation.manager'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; + +@Injectable() +export class ReconciliationDataOrchestrator { + constructor( + private updateManager: UpdateReconciliationManager, + private confirmManager: ConfirmReconciliationManager, + private cancelManager: CancelReconciliationManager, + private batchConfirmManager: BatchConfirmReconciliationManager, + private batchCancelManager: BatchCancelReconciliationManager, + private recapManager: RecapReconciliationManager, + private serviceData: TransactionDataService, + ) {} + + async update(dataId, data): Promise { + this.updateManager.setData(dataId, data); + this.updateManager.setService(this.serviceData, TABLE_NAME.TRANSACTION); + await this.updateManager.execute(); + return this.updateManager.getResult(); + } + + async recap() { + const data = new TransactionModel(); + this.recapManager.setData(data); + this.recapManager.setService(this.serviceData, TABLE_NAME.TRANSACTION); + await this.recapManager.execute(); + return this.recapManager.getResult(); + } + + async confirm(dataId): Promise { + this.confirmManager.setData(dataId, STATUS.CONFIRMED); + this.confirmManager.setService(this.serviceData, TABLE_NAME.TRANSACTION); + await this.confirmManager.execute(); + return this.confirmManager.getResult(); + } + + async batchConfirm(dataIds: string[]): Promise { + this.batchConfirmManager.setData(dataIds, STATUS.CONFIRMED); + this.batchConfirmManager.setService( + this.serviceData, + TABLE_NAME.TRANSACTION, + ); + await this.batchConfirmManager.execute(); + return this.batchConfirmManager.getResult(); + } + + async cancel(dataId): Promise { + this.cancelManager.setData(dataId, STATUS.REJECTED); + this.cancelManager.setService(this.serviceData, TABLE_NAME.TRANSACTION); + await this.cancelManager.execute(); + return this.cancelManager.getResult(); + } + + async batchCancel(dataIds: string[]): Promise { + this.batchCancelManager.setData(dataIds, STATUS.REJECTED); + this.batchCancelManager.setService( + this.serviceData, + TABLE_NAME.TRANSACTION, + ); + await this.batchCancelManager.execute(); + return this.batchCancelManager.getResult(); + } +} diff --git a/src/modules/transaction/reconciliation/domain/usecases/reconciliation-read.orchestrator.ts b/src/modules/transaction/reconciliation/domain/usecases/reconciliation-read.orchestrator.ts new file mode 100644 index 0000000..c5372dc --- /dev/null +++ b/src/modules/transaction/reconciliation/domain/usecases/reconciliation-read.orchestrator.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@nestjs/common'; +import { IndexReconciliationManager } from './managers/index-reconciliation.manager'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { BaseReadOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-read.orchestrator'; +import { DetailReconciliationManager } from './managers/detail-reconciliation.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; +import { TransactionReadService } from 'src/modules/transaction/transaction/data/services/transaction-read.service'; + +@Injectable() +export class ReconciliationReadOrchestrator extends BaseReadOrchestrator { + constructor( + private indexManager: IndexReconciliationManager, + private detailManager: DetailReconciliationManager, + private serviceData: TransactionReadService, + ) { + super(); + } + + async index(params): Promise> { + this.indexManager.setFilterParam(params); + this.indexManager.setService(this.serviceData, TABLE_NAME.TRANSACTION); + await this.indexManager.execute(); + return this.indexManager.getResult(); + } + + async detail(dataId: string): Promise { + this.detailManager.setData(dataId); + this.detailManager.setService(this.serviceData, TABLE_NAME.TRANSACTION); + await this.detailManager.execute(); + return this.detailManager.getResult(); + } +} diff --git a/src/modules/transaction/reconciliation/index.ts b/src/modules/transaction/reconciliation/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/transaction/reconciliation/infrastructure/dto/filter-reconciliation.dto.ts b/src/modules/transaction/reconciliation/infrastructure/dto/filter-reconciliation.dto.ts new file mode 100644 index 0000000..c9b6c2e --- /dev/null +++ b/src/modules/transaction/reconciliation/infrastructure/dto/filter-reconciliation.dto.ts @@ -0,0 +1,60 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; +import { BaseFilterDto } from 'src/core/modules/infrastructure/dto/base-filter.dto'; +import { FilterTransactionEntity } from 'src/modules/transaction/transaction/domain/entities/filter-transaction.entity'; + +export class FilterReconciliationDto + extends BaseFilterDto + implements FilterTransactionEntity +{ + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + customer_names: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + confirm_by_names: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + payment_banks: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + payment_bank_numbers: string[]; + + @ApiProperty({ type: Date, required: false }) + payment_date_from: Date; + + @ApiProperty({ type: Date, required: false }) + payment_date_to: Date; + + @ApiProperty({ type: Number, required: false }) + couner_no: number; + + @ApiProperty({ type: String, required: false }) + payment_type: string; + + @ApiProperty({ type: Date, required: false }) + confirmation_date_from: Date; + + @ApiProperty({ type: Date, required: false }) + confirmation_date_to: Date; + + @ApiProperty({ type: String, required: false }) + transaction_type: string; + + @ApiProperty({ type: String, required: false }) + payment_via: string; + + @ApiProperty({ type: String, required: false }) + payment_bank: string; +} diff --git a/src/modules/transaction/reconciliation/infrastructure/dto/reconciliation.dto.ts b/src/modules/transaction/reconciliation/infrastructure/dto/reconciliation.dto.ts new file mode 100644 index 0000000..a8226e8 --- /dev/null +++ b/src/modules/transaction/reconciliation/infrastructure/dto/reconciliation.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class UpdateReconciliationDto { + @ApiProperty({ + type: Date, + required: true, + example: '2024/06/06', + }) + payment_date: Date; + + @ApiProperty({ + type: Number, + required: false, + example: 350000, + }) + reconciliation_mdr: number; +} diff --git a/src/modules/transaction/reconciliation/infrastructure/reconciliation-data.controller.ts b/src/modules/transaction/reconciliation/infrastructure/reconciliation-data.controller.ts new file mode 100644 index 0000000..15e7f1e --- /dev/null +++ b/src/modules/transaction/reconciliation/infrastructure/reconciliation-data.controller.ts @@ -0,0 +1,58 @@ +import { + Body, + Controller, + Delete, + Param, + Patch, + Post, + Put, +} from '@nestjs/common'; +import { ReconciliationDataOrchestrator } from '../domain/usecases/reconciliation-data.orchestrator'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto'; +import { Public } from 'src/core/guards'; +import { TransactionEntity } from '../../transaction/domain/entities/transaction.entity'; +import { UpdateReconciliationDto } from './dto/reconciliation.dto'; + +@ApiTags(`${MODULE_NAME.RECONCILIATION.split('-').join(' ')} - data`) +@Controller(`v1/${MODULE_NAME.RECONCILIATION}`) +@Public(false) +@ApiBearerAuth('JWT') +export class ReconciliationDataController { + constructor(private orchestrator: ReconciliationDataOrchestrator) {} + + @Post('/recap-transaction') + async recap(): Promise { + return await this.orchestrator.recap(); + } + + @Put('/batch-confirm') + async batchConfirm(@Body() body: BatchIdsDto): Promise { + return await this.orchestrator.batchConfirm(body.ids); + } + + @Patch(':id/confirm') + async confirm(@Param('id') dataId: string): Promise { + return await this.orchestrator.confirm(dataId); + } + + @Patch(':id/cancel') + async cancel(@Param('id') dataId: string): Promise { + return await this.orchestrator.cancel(dataId); + } + + @Put('/batch-cancel') + async batchCancel(@Body() body: BatchIdsDto): Promise { + return await this.orchestrator.batchCancel(body.ids); + } + + @Put(':id') + async update( + @Param('id') dataId: string, + @Body() data: UpdateReconciliationDto, + ): Promise { + return await this.orchestrator.update(dataId, data); + } +} diff --git a/src/modules/transaction/reconciliation/infrastructure/reconciliation-read.controller.ts b/src/modules/transaction/reconciliation/infrastructure/reconciliation-read.controller.ts new file mode 100644 index 0000000..4b3f1fb --- /dev/null +++ b/src/modules/transaction/reconciliation/infrastructure/reconciliation-read.controller.ts @@ -0,0 +1,30 @@ +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { FilterReconciliationDto } from './dto/filter-reconciliation.dto'; +import { Pagination } from 'src/core/response'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { ReconciliationReadOrchestrator } from '../domain/usecases/reconciliation-read.orchestrator'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { Public } from 'src/core/guards'; +import { TransactionEntity } from '../../transaction/domain/entities/transaction.entity'; + +@ApiTags(`${MODULE_NAME.RECONCILIATION.split('-').join(' ')} - read`) +@Controller(`v1/${MODULE_NAME.RECONCILIATION}`) +@Public(false) +@ApiBearerAuth('JWT') +export class ReconciliationReadController { + constructor(private orchestrator: ReconciliationReadOrchestrator) {} + + @Get() + @Pagination() + async index( + @Query() params: FilterReconciliationDto, + ): Promise> { + return await this.orchestrator.index(params); + } + + @Get(':id') + async detail(@Param('id') id: string): Promise { + return await this.orchestrator.detail(id); + } +} diff --git a/src/modules/transaction/reconciliation/reconciliation.module.ts b/src/modules/transaction/reconciliation/reconciliation.module.ts new file mode 100644 index 0000000..e47d163 --- /dev/null +++ b/src/modules/transaction/reconciliation/reconciliation.module.ts @@ -0,0 +1,50 @@ +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 { ReconciliationReadController } from './infrastructure/reconciliation-read.controller'; +import { ReconciliationReadOrchestrator } from './domain/usecases/reconciliation-read.orchestrator'; +import { ReconciliationDataController } from './infrastructure/reconciliation-data.controller'; +import { ReconciliationDataOrchestrator } from './domain/usecases/reconciliation-data.orchestrator'; +import { CqrsModule } from '@nestjs/cqrs'; +import { IndexReconciliationManager } from './domain/usecases/managers/index-reconciliation.manager'; +import { UpdateReconciliationManager } from './domain/usecases/managers/update-reconciliation.manager'; +import { ConfirmReconciliationManager } from './domain/usecases/managers/confirm-reconciliation.manager'; +import { DetailReconciliationManager } from './domain/usecases/managers/detail-reconciliation.manager'; +import { TransactionModel } from '../transaction/data/models/transaction.model'; +import { TransactionDataService } from '../transaction/data/services/transaction-data.service'; +import { TransactionReadService } from '../transaction/data/services/transaction-read.service'; +import { CancelReconciliationManager } from './domain/usecases/managers/cancel-reconciliation.manager'; +import { BatchCancelReconciliationManager } from './domain/usecases/managers/batch-cancel-reconciliation.manager'; +import { BatchConfirmReconciliationManager } from './domain/usecases/managers/batch-confirm-reconciliation.manager'; +import { RecapReconciliationManager } from './domain/usecases/managers/recap-reconciliation.manager'; +import { RecapPosTransactionHandler } from './domain/usecases/handlers/recap-pos-transaction.handler'; +import { SalesPriceFormulaDataService } from '../sales-price-formula/data/services/sales-price-formula-data.service'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature([TransactionModel], CONNECTION_NAME.DEFAULT), + CqrsModule, + ], + controllers: [ReconciliationDataController, ReconciliationReadController], + providers: [ + RecapPosTransactionHandler, + + IndexReconciliationManager, + DetailReconciliationManager, + UpdateReconciliationManager, + ConfirmReconciliationManager, + BatchConfirmReconciliationManager, + CancelReconciliationManager, + BatchCancelReconciliationManager, + RecapReconciliationManager, + + TransactionDataService, + TransactionReadService, + + ReconciliationDataOrchestrator, + ReconciliationReadOrchestrator, + ], +}) +export class ReconciliationModule {} diff --git a/src/modules/transaction/refund/constants.ts b/src/modules/transaction/refund/constants.ts new file mode 100644 index 0000000..0c9b67b --- /dev/null +++ b/src/modules/transaction/refund/constants.ts @@ -0,0 +1,4 @@ +export enum RefundType { + BOOKING = 'pengembalian booking', + WAHANA = 'pengembalian wahana', +} diff --git a/src/modules/transaction/refund/data/models/refund-item.model.ts b/src/modules/transaction/refund/data/models/refund-item.model.ts new file mode 100644 index 0000000..5ab17c8 --- /dev/null +++ b/src/modules/transaction/refund/data/models/refund-item.model.ts @@ -0,0 +1,38 @@ +import { BaseCoreModel } from 'src/core/modules/data/model/base-core.model'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from 'typeorm'; +import { RefundItemEntity } from '../../domain/entities/refund-item.entity'; +import { TransactionItemModel } from 'src/modules/transaction/transaction/data/models/transaction-item.model'; +import { RefundModel } from './refund.model'; + +@Entity(TABLE_NAME.REFUND_ITEM) +export class RefundItemModel + extends BaseCoreModel + implements RefundItemEntity +{ + @Column('decimal', { name: 'qty_refund', nullable: true }) + qty_refund: number; + + @Column('decimal', { name: 'refund_total', nullable: true }) + refund_total: number; + + // transaction to refund + @Column('decimal', { name: 'refund_item_id', nullable: true }) + refund_item_id: number; + @ManyToOne(() => RefundModel, (model) => model.refund_items, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + @JoinColumn({ name: 'refund_item_id' }) + refund: RefundModel; + + // transaction to transaction item + @Column('varchar', { name: 'transaction_item_id', nullable: true }) + transaction_item_id: string; + @OneToOne(() => TransactionItemModel, (model) => model.refund, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + @JoinColumn({ name: 'transaction_item_id' }) + transaction_item: TransactionItemModel; +} diff --git a/src/modules/transaction/refund/data/models/refund.model.ts b/src/modules/transaction/refund/data/models/refund.model.ts new file mode 100644 index 0000000..4740870 --- /dev/null +++ b/src/modules/transaction/refund/data/models/refund.model.ts @@ -0,0 +1,63 @@ +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { RefundEntity } from '../../domain/entities/refund.entity'; +import { Column, Entity, JoinColumn, OneToMany, OneToOne } from 'typeorm'; +import { BaseStatusModel } from 'src/core/modules/data/model/base-status.model'; +import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; +import { RefundItemModel } from './refund-item.model'; +import { RefundType } from '../../constants'; + +@Entity(TABLE_NAME.REFUND) +export class RefundModel + extends BaseStatusModel + implements RefundEntity +{ + @Column('enum', { + name: 'type', + enum: RefundType, + default: RefundType.BOOKING, + }) + type: RefundType; + + @Column('varchar', { name: 'code', nullable: true }) + code: string; + + @Column('date', { name: 'request_date', nullable: true }) + request_date: Date; + + @Column('date', { name: 'refund_date', nullable: true }) + refund_date: Date; + + @Column('decimal', { name: 'refund_sub_total', nullable: true }) + refund_sub_total: number; + + @Column('decimal', { name: 'refund_total', nullable: true }) + refund_total: number; + + // bank info + @Column('varchar', { name: 'bank_name', nullable: true }) + bank_name: string; + + @Column('varchar', { name: 'bank_account_name', nullable: true }) + bank_account_name: string; + + @Column('varchar', { name: 'bank_account_number', nullable: true }) + bank_account_number: string; + + // relations to item + @OneToMany(() => RefundItemModel, (model) => model.refund, { + cascade: true, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + refund_items: RefundItemModel[]; + + // relation to transaction + @Column('varchar', { name: 'transaction_id', nullable: true }) + transaction_id: string; + @OneToOne(() => TransactionModel, (model) => model.refund, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + @JoinColumn({ name: 'transaction_id' }) + transaction: TransactionModel; +} diff --git a/src/modules/transaction/refund/data/services/refund-data.service.ts b/src/modules/transaction/refund/data/services/refund-data.service.ts new file mode 100644 index 0000000..34e196e --- /dev/null +++ b/src/modules/transaction/refund/data/services/refund-data.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; +import { RefundEntity } from '../../domain/entities/refund.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { RefundModel } from '../models/refund.model'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { Repository } from 'typeorm'; + +@Injectable() +export class RefundDataService extends BaseDataService { + constructor( + @InjectRepository(RefundModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/transaction/refund/data/services/refund-read.service.ts b/src/modules/transaction/refund/data/services/refund-read.service.ts new file mode 100644 index 0000000..7bd5ec4 --- /dev/null +++ b/src/modules/transaction/refund/data/services/refund-read.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { RefundEntity } from '../../domain/entities/refund.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { RefundModel } from '../models/refund.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 RefundReadService extends BaseReadService { + constructor( + @InjectRepository(RefundModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/transaction/refund/domain/entities/event/refund-change-status.event.ts b/src/modules/transaction/refund/domain/entities/event/refund-change-status.event.ts new file mode 100644 index 0000000..66e8ef2 --- /dev/null +++ b/src/modules/transaction/refund/domain/entities/event/refund-change-status.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class RefundChangeStatusEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/transaction/refund/domain/entities/event/refund-created.event.ts b/src/modules/transaction/refund/domain/entities/event/refund-created.event.ts new file mode 100644 index 0000000..2621c9d --- /dev/null +++ b/src/modules/transaction/refund/domain/entities/event/refund-created.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class RefundCreatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/transaction/refund/domain/entities/event/refund-deleted.event.ts b/src/modules/transaction/refund/domain/entities/event/refund-deleted.event.ts new file mode 100644 index 0000000..dce6938 --- /dev/null +++ b/src/modules/transaction/refund/domain/entities/event/refund-deleted.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class RefundDeletedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/transaction/refund/domain/entities/event/refund-updated.event.ts b/src/modules/transaction/refund/domain/entities/event/refund-updated.event.ts new file mode 100644 index 0000000..f41534f --- /dev/null +++ b/src/modules/transaction/refund/domain/entities/event/refund-updated.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class RefundUpdatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/transaction/refund/domain/entities/filter-refund.entity.ts b/src/modules/transaction/refund/domain/entities/filter-refund.entity.ts new file mode 100644 index 0000000..4232869 --- /dev/null +++ b/src/modules/transaction/refund/domain/entities/filter-refund.entity.ts @@ -0,0 +1,3 @@ +import { BaseFilterEntity } from 'src/core/modules/domain/entities/base-filter.entity'; + +export interface FilterRefundEntity extends BaseFilterEntity {} diff --git a/src/modules/transaction/refund/domain/entities/refund-item.entity.ts b/src/modules/transaction/refund/domain/entities/refund-item.entity.ts new file mode 100644 index 0000000..880dc80 --- /dev/null +++ b/src/modules/transaction/refund/domain/entities/refund-item.entity.ts @@ -0,0 +1,7 @@ +import { BaseCoreEntity } from 'src/core/modules/domain/entities/base-core.entity'; + +export interface RefundItemEntity extends BaseCoreEntity { + qty_refund: number; + + transaction_item_id?: string; +} diff --git a/src/modules/transaction/refund/domain/entities/refund.entity.ts b/src/modules/transaction/refund/domain/entities/refund.entity.ts new file mode 100644 index 0000000..c9bf720 --- /dev/null +++ b/src/modules/transaction/refund/domain/entities/refund.entity.ts @@ -0,0 +1,17 @@ +import { BaseStatusEntity } from 'src/core/modules/domain/entities/base-status.entity'; +import { RefundType } from '../../constants'; + +export interface RefundEntity extends BaseStatusEntity { + type: RefundType; + code: string; + request_date: Date; + refund_date: Date; + refund_sub_total: number; + refund_total: number; + + bank_name: string; + bank_account_name: string; + bank_account_number: string; + + transaction_id: string; +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/batch-cancel-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/batch-cancel-refund.manager.ts new file mode 100644 index 0000000..6197a2d --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/batch-cancel-refund.manager.ts @@ -0,0 +1,57 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { RefundEntity } from '../../entities/refund.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { RefundModel } from '../../../data/models/refund.model'; +import { RefundChangeStatusEvent } from '../../entities/event/refund-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class BatchCancelRefundManager extends BaseBatchUpdateStatusManager { + validateData(data: RefundEntity): Promise { + if (![STATUS.REFUNDED, STATUS.PENDING].includes(data.status)) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only data with status ${STATUS.REFUNDED} and ${STATUS.PENDING} can be cancelled`, + error: 'Unprocessable Entity', + }); + } + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return RefundModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: RefundChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/batch-confirm-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/batch-confirm-refund.manager.ts new file mode 100644 index 0000000..678566a --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/batch-confirm-refund.manager.ts @@ -0,0 +1,78 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { RefundEntity } from '../../entities/refund.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { RefundModel } from '../../../data/models/refund.model'; +import { RefundChangeStatusEvent } from '../../entities/event/refund-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class BatchConfirmRefundManager extends BaseBatchUpdateStatusManager { + async validateData(data: RefundEntity): Promise { + if (data?.['transaction']?.status != STATUS.SETTLED) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only transaction with status ${STATUS.SETTLED} can be refund`, + error: 'Unprocessable Entity', + }); + } else if (![STATUS.DRAFT, STATUS.PENDING].includes(data.status)) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only data with status ${STATUS.DRAFT} and ${STATUS.PENDING} can be confirmed`, + error: 'Unprocessable Entity', + }); + } + + if (this.data.status == STATUS.DRAFT) { + Object.assign(this.data, { + code: `RF-${data?.['transaction']?.invoice_code.split('-')[1]}`, + request_date: new Date(), + status: STATUS.PENDING, + }); + } else if (this.data.status == STATUS.PENDING) { + Object.assign(this.data, { + refund_date: new Date(), + status: STATUS.REFUNDED, + }); + } + return; + } + + beforeProcess(): Promise { + this.relations = ['transaction']; + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return RefundModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: RefundChangeStatusEvent, + relations: ['transaction'], + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/batch-delete-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/batch-delete-refund.manager.ts new file mode 100644 index 0000000..658d0b9 --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/batch-delete-refund.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchDeleteManager } from 'src/core/modules/domain/usecase/managers/base-batch-delete.manager'; +import { RefundEntity } from '../../entities/refund.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { RefundModel } from '../../../data/models/refund.model'; +import { RefundDeletedEvent } from '../../entities/event/refund-deleted.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchDeleteRefundManager extends BaseBatchDeleteManager { + async beforeProcess(): Promise { + return; + } + + async validateData(data: RefundEntity): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return RefundModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: RefundDeletedEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/cancel-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/cancel-refund.manager.ts new file mode 100644 index 0000000..ec40de3 --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/cancel-refund.manager.ts @@ -0,0 +1,57 @@ +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager'; +import { RefundEntity } from '../../entities/refund.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { RefundModel } from '../../../data/models/refund.model'; +import { RefundChangeStatusEvent } from '../../entities/event/refund-change-status.event'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class CancelRefundManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.code}`; + } + + async validateProcess(): Promise { + if (![STATUS.REFUNDED, STATUS.PENDING].includes(this.data.status)) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only data with status ${STATUS.REFUNDED} and ${STATUS.PENDING} can be cancelled`, + error: 'Unprocessable Entity', + }); + } + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return RefundModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: RefundChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/confirm-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/confirm-refund.manager.ts new file mode 100644 index 0000000..1de469c --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/confirm-refund.manager.ts @@ -0,0 +1,85 @@ +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager'; +import { RefundEntity } from '../../entities/refund.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { RefundModel } from '../../../data/models/refund.model'; +import { RefundChangeStatusEvent } from '../../entities/event/refund-change-status.event'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class ConfirmRefundManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.code}`; + } + + async validateProcess(): Promise { + if (![STATUS.DRAFT, STATUS.PENDING].includes(this.oldData.status)) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only data with status ${STATUS.DRAFT} and ${STATUS.PENDING} can be confirmed`, + error: 'Unprocessable Entity', + }); + } + return; + } + + async beforeProcess(): Promise { + const data = await this.dataService.getOneByOptions({ + where: { + id: this.data.id, + }, + relations: ['transaction'], + }); + + if (data.transaction.status != STATUS.SETTLED) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only transaction with status ${STATUS.SETTLED} can be refund`, + error: 'Unprocessable Entity', + }); + } + + if (data.status == STATUS.DRAFT) { + Object.assign(this.data, { + code: `RF-${data.transaction?.invoice_code?.split('-')[1]}`, + request_date: new Date(), + status: STATUS.PENDING, + }); + } else if (data.status == STATUS.PENDING) { + Object.assign(this.data, { + refund_date: new Date(), + status: STATUS.REFUNDED, + }); + } + + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return RefundModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: RefundChangeStatusEvent, + relations: ['transaction'], + }, + ]; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/create-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/create-refund.manager.ts new file mode 100644 index 0000000..8e65106 --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/create-refund.manager.ts @@ -0,0 +1,87 @@ +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { RefundEntity } from '../../entities/refund.entity'; +import { RefundModel } from '../../../data/models/refund.model'; +import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager'; +import { RefundCreatedEvent } from '../../entities/event/refund-created.event'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class CreateRefundManager extends BaseCreateManager { + async beforeProcess(): Promise { + const refund_items = this.data.refund_items?.map((item) => { + return { + transaction_item: item, + qty_refund: item.qty_refund, + refund_total: item.refund_total, + refund_sub_total: item.refund_sub_total, + }; + }); + + Object.assign(this.data, { + refund_items: refund_items, + }); + + const exist = await this.dataService.getOneByOptions({ + where: { + transaction_id: this.data.transaction.id, + }, + }); + if (exist) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! refund transaction with invoice ${this.data.transaction.invoice_code} already exist`, + error: 'Unprocessable Entity', + }); + } + + const transaction = await this.dataServiceFirstOpt.getOneByOptions({ + where: { + id: this.data.transaction.id, + status: STATUS.SETTLED, + }, + }); + + if (!transaction) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only transaction with status ${STATUS.SETTLED} can be refund`, + error: 'Unprocessable Entity', + }); + } + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: RefundCreatedEvent, + data: this.data, + }, + ]; + } + + get entityTarget(): any { + return RefundModel; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/delete-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/delete-refund.manager.ts new file mode 100644 index 0000000..0015808 --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/delete-refund.manager.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDeleteManager } from 'src/core/modules/domain/usecase/managers/base-delete.manager'; +import { RefundEntity } from '../../entities/refund.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { RefundModel } from '../../../data/models/refund.model'; +import { RefundDeletedEvent } from '../../entities/event/refund-deleted.event'; + +@Injectable() +export class DeleteRefundManager 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 RefundModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: RefundDeletedEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/detail-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/detail-refund.manager.ts new file mode 100644 index 0000000..88c3e89 --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/detail-refund.manager.ts @@ -0,0 +1,65 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDetailManager } from 'src/core/modules/domain/usecase/managers/base-detail.manager'; +import { RefundEntity } from '../../entities/refund.entity'; +import { RelationParam } from 'src/core/modules/domain/entities/base-filter.entity'; +import { mappingTransaction } from 'src/modules/transaction/transaction/domain/usecases/managers/helpers/mapping-transaction.helper'; + +@Injectable() +export class DetailRefundManager extends BaseDetailManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + mappingTransaction(this.result['transaction']); + return; + } + + get relations(): RelationParam { + return { + // relation only join (for query purpose) + joinRelations: [], + + // relation join and select (relasi yang ingin ditampilkan), + selectRelations: ['transaction', 'transaction.items', 'items.refund'], + + // relation yang hanya ingin dihitung (akan return number) + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.code`, + `${this.tableName}.status`, + `${this.tableName}.type`, + + `${this.tableName}.request_date`, + `${this.tableName}.refund_date`, + `${this.tableName}.created_at`, + `${this.tableName}.updated_at`, + `${this.tableName}.creator_name`, + `${this.tableName}.editor_name`, + + `${this.tableName}.refund_total`, + `${this.tableName}.bank_name`, + `${this.tableName}.bank_account_name`, + `${this.tableName}.bank_account_number`, + + 'transaction', + 'items', + 'refund', + ]; + } + + get setFindProperties(): any { + return { + id: this.dataId, + }; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/index-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/index-refund.manager.ts new file mode 100644 index 0000000..2fd80a0 --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/index-refund.manager.ts @@ -0,0 +1,145 @@ +import { Injectable } from '@nestjs/common'; +import { BaseIndexManager } from 'src/core/modules/domain/usecase/managers/base-index.manager'; +import { RefundEntity } from '../../entities/refund.entity'; +import { SelectQueryBuilder } from 'typeorm'; +import { + Param, + RelationParam, +} from 'src/core/modules/domain/entities/base-filter.entity'; +import { BetweenQueryHelper } from 'src/core/helpers/query/between-query.helper'; + +@Injectable() +export class IndexRefundManager extends BaseIndexManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + this.result?.data?.map((item) => { + delete item['transaction']?.id; + Object.assign(item, { + ...item['transaction'], + }); + + delete item['transaction']; + }); + return; + } + + get relations(): RelationParam { + return { + // relation only join (for query purpose) + joinRelations: [], + + // relation join and select (relasi yang ingin ditampilkan), + selectRelations: ['transaction'], + + // relation yang hanya ingin dihitung (akan return number) + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.code`, + `${this.tableName}.status`, + `${this.tableName}.type`, + + `${this.tableName}.request_date`, + `${this.tableName}.refund_date`, + `${this.tableName}.created_at`, + `${this.tableName}.updated_at`, + `${this.tableName}.creator_name`, + `${this.tableName}.editor_name`, + + `${this.tableName}.refund_total`, + `${this.tableName}.bank_name`, + `${this.tableName}.bank_account_name`, + `${this.tableName}.bank_account_number`, + + `transaction.id`, + `transaction.invoice_code`, + `transaction.settlement_date`, + `transaction.payment_total`, + `transaction.customer_name`, + `transaction.customer_phone`, + `transaction.customer_email`, + `transaction.customer_description`, + ]; + } + + get specificFilter(): Param[] { + return [ + { + cols: `${this.tableName}.code`, + data: this.filterParam.codes, + }, + { + cols: `transaction.invoice_code`, + data: this.filterParam.invoice_codes, + }, + { + cols: `${this.tableName}.bank_name`, + data: this.filterParam.bank_names, + }, + { + cols: `${this.tableName}.bank_account_name`, + data: this.filterParam.bank_account_names, + }, + { + cols: `${this.tableName}.bank_account_number`, + data: this.filterParam.bank_account_numbers, + }, + { + cols: `transaction.customer_name`, + data: this.filterParam.contact_names, + }, + { + cols: `${this.tableName}.creator_name`, + data: this.filterParam.creator_names, + }, + ]; + } + + setQueryFilter( + queryBuilder: SelectQueryBuilder, + ): SelectQueryBuilder { + if (!!this.filterParam.refund_date_from) + new BetweenQueryHelper( + queryBuilder, + this.tableName, + 'refund_date', + this.filterParam.refund_date_from, + this.filterParam.refund_date_to ?? this.filterParam.refund_date_from, + 'refund_date', + ).getQuery(); + + if (!!this.filterParam.request_date_from) + new BetweenQueryHelper( + queryBuilder, + this.tableName, + 'request_date', + this.filterParam.request_date_from, + this.filterParam.request_date_to ?? this.filterParam.request_date_from, + 'request_date', + ).getQuery(); + + if (!!this.filterParam.settlement_date_from) + new BetweenQueryHelper( + queryBuilder, + 'transaction', + 'settlement_date', + this.filterParam.settlement_date_from, + this.filterParam.settlement_date_to ?? + this.filterParam.settlement_date_from, + 'settlement_date', + ).getQuery(); + + return queryBuilder; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/managers/update-refund.manager.ts b/src/modules/transaction/refund/domain/usecases/managers/update-refund.manager.ts new file mode 100644 index 0000000..86e8602 --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/managers/update-refund.manager.ts @@ -0,0 +1,77 @@ +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { BaseUpdateManager } from 'src/core/modules/domain/usecase/managers/base-update.manager'; +import { RefundEntity } from '../../entities/refund.entity'; +import { RefundModel } from '../../../data/models/refund.model'; +import { RefundUpdatedEvent } from '../../entities/event/refund-updated.event'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class UpdateRefundManager extends BaseUpdateManager { + async validateProcess(): Promise { + const transaction = await this.dataServiceFirstOpt.getOneByOptions({ + where: { + id: this.data.transaction.id, + status: STATUS.SETTLED, + }, + }); + + if (!transaction) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only transaction with status ${STATUS.SETTLED} can be refund`, + error: 'Unprocessable Entity', + }); + } + return; + } + + async beforeProcess(): Promise { + const refund_items = this.data.refund_items?.map((item) => { + return { + transaction_item: item, + qty_refund: item.qty_refund, + refund_total: item.refund_total, + refund_sub_total: item.refund_sub_total, + }; + }); + + Object.assign(this.data, { + refund_items: refund_items ?? [], + }); + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get entityTarget(): any { + return RefundModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: RefundUpdatedEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/transaction/refund/domain/usecases/refund-data.orchestrator.ts b/src/modules/transaction/refund/domain/usecases/refund-data.orchestrator.ts new file mode 100644 index 0000000..c4500a1 --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/refund-data.orchestrator.ts @@ -0,0 +1,95 @@ +import { Injectable } from '@nestjs/common'; +import { CreateRefundManager } from './managers/create-refund.manager'; +import { RefundDataService } from '../../data/services/refund-data.service'; +import { RefundEntity } from '../entities/refund.entity'; +import { DeleteRefundManager } from './managers/delete-refund.manager'; +import { UpdateRefundManager } from './managers/update-refund.manager'; +import { ConfirmRefundManager } from './managers/confirm-refund.manager'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchConfirmRefundManager } from './managers/batch-confirm-refund.manager'; +import { BatchDeleteRefundManager } from './managers/batch-delete-refund.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { CancelRefundManager } from './managers/cancel-refund.manager'; +import { BatchCancelRefundManager } from './managers/batch-cancel-refund.manager'; +import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service'; + +@Injectable() +export class RefundDataOrchestrator { + constructor( + private createManager: CreateRefundManager, + private updateManager: UpdateRefundManager, + private deleteManager: DeleteRefundManager, + private cancelManager: CancelRefundManager, + private confirmManager: ConfirmRefundManager, + private batchDeleteManager: BatchDeleteRefundManager, + private batchCancelManager: BatchCancelRefundManager, + private batchConfirmManager: BatchConfirmRefundManager, + private serviceData: RefundDataService, + private transactionDataService: TransactionDataService, + ) {} + + async create(data): Promise { + this.createManager.setData(data); + this.createManager.setService( + this.serviceData, + TABLE_NAME.REFUND, + this.transactionDataService, + ); + await this.createManager.execute(); + return this.createManager.getResult(); + } + + async update(dataId, data): Promise { + this.updateManager.setData(dataId, data); + this.updateManager.setService( + this.serviceData, + TABLE_NAME.REFUND, + this.transactionDataService, + ); + await this.updateManager.execute(); + return this.updateManager.getResult(); + } + + async delete(dataId): Promise { + this.deleteManager.setData(dataId); + this.deleteManager.setService(this.serviceData, TABLE_NAME.REFUND); + await this.deleteManager.execute(); + return this.deleteManager.getResult(); + } + + async batchDelete(dataIds: string[]): Promise { + this.batchDeleteManager.setData(dataIds); + this.batchDeleteManager.setService(this.serviceData, TABLE_NAME.REFUND); + await this.batchDeleteManager.execute(); + return this.batchDeleteManager.getResult(); + } + + async cancel(dataId): Promise { + this.cancelManager.setData(dataId, STATUS.CANCEL); + this.cancelManager.setService(this.serviceData, TABLE_NAME.REFUND); + await this.cancelManager.execute(); + return this.cancelManager.getResult(); + } + + async batchCancel(dataIds: string[]): Promise { + this.batchCancelManager.setData(dataIds, STATUS.CANCEL); + this.batchCancelManager.setService(this.serviceData, TABLE_NAME.REFUND); + await this.batchCancelManager.execute(); + return this.batchCancelManager.getResult(); + } + + async confirm(dataId): Promise { + this.confirmManager.setData(dataId, STATUS.PENDING); + this.confirmManager.setService(this.serviceData, TABLE_NAME.REFUND); + await this.confirmManager.execute(); + return this.confirmManager.getResult(); + } + + async batchConfirm(dataIds: string[]): Promise { + this.batchConfirmManager.setData(dataIds, STATUS.PENDING); + this.batchConfirmManager.setService(this.serviceData, TABLE_NAME.REFUND); + await this.batchConfirmManager.execute(); + return this.batchConfirmManager.getResult(); + } +} diff --git a/src/modules/transaction/refund/domain/usecases/refund-read.orchestrator.ts b/src/modules/transaction/refund/domain/usecases/refund-read.orchestrator.ts new file mode 100644 index 0000000..74af965 --- /dev/null +++ b/src/modules/transaction/refund/domain/usecases/refund-read.orchestrator.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@nestjs/common'; +import { IndexRefundManager } from './managers/index-refund.manager'; +import { RefundReadService } from '../../data/services/refund-read.service'; +import { RefundEntity } from '../entities/refund.entity'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { BaseReadOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-read.orchestrator'; +import { DetailRefundManager } from './managers/detail-refund.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@Injectable() +export class RefundReadOrchestrator extends BaseReadOrchestrator { + constructor( + private indexManager: IndexRefundManager, + private detailManager: DetailRefundManager, + private serviceData: RefundReadService, + ) { + super(); + } + + async index(params): Promise> { + this.indexManager.setFilterParam(params); + this.indexManager.setService(this.serviceData, TABLE_NAME.REFUND); + await this.indexManager.execute(); + return this.indexManager.getResult(); + } + + async detail(dataId: string): Promise { + this.detailManager.setData(dataId); + this.detailManager.setService(this.serviceData, TABLE_NAME.REFUND); + await this.detailManager.execute(); + return this.detailManager.getResult(); + } +} diff --git a/src/modules/transaction/refund/index.ts b/src/modules/transaction/refund/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/transaction/refund/infrastructure/dto/filter-refund.dto.ts b/src/modules/transaction/refund/infrastructure/dto/filter-refund.dto.ts new file mode 100644 index 0000000..aa2cb41 --- /dev/null +++ b/src/modules/transaction/refund/infrastructure/dto/filter-refund.dto.ts @@ -0,0 +1,67 @@ +import { BaseFilterDto } from 'src/core/modules/infrastructure/dto/base-filter.dto'; +import { FilterRefundEntity } from '../../domain/entities/filter-refund.entity'; +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; + +export class FilterRefundDto + extends BaseFilterDto + implements FilterRefundEntity +{ + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + codes: string[]; + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + invoice_codes: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + bank_names: string[]; + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + bank_account_names: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + bank_account_numbers: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + contact_names: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + creator_names: string[]; + + @ApiProperty({ type: Date, required: false }) + refund_date_from: Date; + + @ApiProperty({ type: Date, required: false }) + refund_date_to: Date; + + @ApiProperty({ type: Date, required: false }) + request_date_from: Date; + + @ApiProperty({ type: Date, required: false }) + request_date_to: Date; + + @ApiProperty({ type: Date, required: false }) + settlement_date_from: Date; + + @ApiProperty({ type: Date, required: false }) + settlement_date_to: Date; +} diff --git a/src/modules/transaction/refund/infrastructure/dto/refund.dto.ts b/src/modules/transaction/refund/infrastructure/dto/refund.dto.ts new file mode 100644 index 0000000..86a3b70 --- /dev/null +++ b/src/modules/transaction/refund/infrastructure/dto/refund.dto.ts @@ -0,0 +1,74 @@ +import { BaseStatusDto } from 'src/core/modules/infrastructure/dto/base-status.dto'; +import { RefundEntity } from '../../domain/entities/refund.entity'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsString, ValidateIf } from 'class-validator'; +import { Exclude } from 'class-transformer'; +import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; +import { RefundType } from '../../constants'; + +export class RefundDto extends BaseStatusDto implements RefundEntity { + @ApiProperty({ + type: String, + required: true, + example: RefundType.BOOKING, + }) + @IsString() + type: RefundType; + + @ApiProperty({ + type: Number, + required: false, + example: 1750000, + }) + refund_sub_total: number; + + @ApiProperty({ + type: Number, + example: 1750000, + }) + @IsNumber() + refund_total: number; + + @ApiProperty({ + type: String, + required: false, + example: 'BCA', + }) + bank_name: string; + + @ApiProperty({ + type: String, + required: false, + example: 'andhika', + }) + bank_account_name: string; + + @ApiProperty({ + type: String, + required: false, + example: '64222456', + }) + bank_account_number: string; + + @ApiProperty({ + type: Object, + required: true, + example: { + id: 'uuid', + invoice_code: 'INV-', + }, + }) + transaction: TransactionEntity; + + @Exclude() + code: string; + + @Exclude() + request_date: Date; + + @Exclude() + refund_date: Date; + + @Exclude() + transaction_id: string; +} diff --git a/src/modules/transaction/refund/infrastructure/refund-data.controller.ts b/src/modules/transaction/refund/infrastructure/refund-data.controller.ts new file mode 100644 index 0000000..9a0fc4f --- /dev/null +++ b/src/modules/transaction/refund/infrastructure/refund-data.controller.ts @@ -0,0 +1,68 @@ +import { + Body, + Controller, + Delete, + Param, + Patch, + Post, + Put, +} from '@nestjs/common'; +import { RefundDataOrchestrator } from '../domain/usecases/refund-data.orchestrator'; +import { RefundDto } from './dto/refund.dto'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { RefundEntity } from '../domain/entities/refund.entity'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto'; +import { Public } from 'src/core/guards'; + +@ApiTags(`${MODULE_NAME.REFUND.split('-').join(' ')} - data`) +@Controller(`v1/${MODULE_NAME.REFUND}`) +@Public(false) +@ApiBearerAuth('JWT') +export class RefundDataController { + constructor(private orchestrator: RefundDataOrchestrator) {} + + @Post() + async create(@Body() data: RefundDto): 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/cancel') + async cancel(@Param('id') dataId: string): Promise { + return await this.orchestrator.cancel(dataId); + } + + @Put('/batch-cancel') + async batchcancel(@Body() body: BatchIdsDto): Promise { + return await this.orchestrator.batchCancel(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); + } + + @Put(':id') + async update( + @Param('id') dataId: string, + @Body() data: RefundDto, + ): Promise { + return await this.orchestrator.update(dataId, data); + } + + @Delete(':id') + async delete(@Param('id') dataId: string): Promise { + return await this.orchestrator.delete(dataId); + } +} diff --git a/src/modules/transaction/refund/infrastructure/refund-read.controller.ts b/src/modules/transaction/refund/infrastructure/refund-read.controller.ts new file mode 100644 index 0000000..c3ec924 --- /dev/null +++ b/src/modules/transaction/refund/infrastructure/refund-read.controller.ts @@ -0,0 +1,30 @@ +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { FilterRefundDto } from './dto/filter-refund.dto'; +import { Pagination } from 'src/core/response'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { RefundEntity } from '../domain/entities/refund.entity'; +import { RefundReadOrchestrator } from '../domain/usecases/refund-read.orchestrator'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { Public } from 'src/core/guards'; + +@ApiTags(`${MODULE_NAME.REFUND.split('-').join(' ')} - read`) +@Controller(`v1/${MODULE_NAME.REFUND}`) +@Public(false) +@ApiBearerAuth('JWT') +export class RefundReadController { + constructor(private orchestrator: RefundReadOrchestrator) {} + + @Get() + @Pagination() + async index( + @Query() params: FilterRefundDto, + ): Promise> { + return await this.orchestrator.index(params); + } + + @Get(':id') + async detail(@Param('id') id: string): Promise { + return await this.orchestrator.detail(id); + } +} diff --git a/src/modules/transaction/refund/refund.module.ts b/src/modules/transaction/refund/refund.module.ts new file mode 100644 index 0000000..c45fc5a --- /dev/null +++ b/src/modules/transaction/refund/refund.module.ts @@ -0,0 +1,57 @@ +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 { RefundDataService } from './data/services/refund-data.service'; +import { RefundReadService } from './data/services/refund-read.service'; +import { RefundReadController } from './infrastructure/refund-read.controller'; +import { RefundReadOrchestrator } from './domain/usecases/refund-read.orchestrator'; +import { RefundDataController } from './infrastructure/refund-data.controller'; +import { RefundDataOrchestrator } from './domain/usecases/refund-data.orchestrator'; +import { CreateRefundManager } from './domain/usecases/managers/create-refund.manager'; +import { CqrsModule } from '@nestjs/cqrs'; +import { IndexRefundManager } from './domain/usecases/managers/index-refund.manager'; +import { DeleteRefundManager } from './domain/usecases/managers/delete-refund.manager'; +import { UpdateRefundManager } from './domain/usecases/managers/update-refund.manager'; +import { ConfirmRefundManager } from './domain/usecases/managers/confirm-refund.manager'; +import { DetailRefundManager } from './domain/usecases/managers/detail-refund.manager'; +import { BatchDeleteRefundManager } from './domain/usecases/managers/batch-delete-refund.manager'; +import { BatchConfirmRefundManager } from './domain/usecases/managers/batch-confirm-refund.manager'; +import { RefundModel } from './data/models/refund.model'; +import { BatchCancelRefundManager } from './domain/usecases/managers/batch-cancel-refund.manager'; +import { CancelRefundManager } from './domain/usecases/managers/cancel-refund.manager'; +import { RefundItemModel } from './data/models/refund-item.model'; +import { TransactionDataService } from '../transaction/data/services/transaction-data.service'; +import { TransactionModel } from '../transaction/data/models/transaction.model'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature( + [RefundModel, RefundItemModel, TransactionModel], + CONNECTION_NAME.DEFAULT, + ), + CqrsModule, + ], + controllers: [RefundDataController, RefundReadController], + providers: [ + IndexRefundManager, + DetailRefundManager, + CreateRefundManager, + DeleteRefundManager, + UpdateRefundManager, + ConfirmRefundManager, + CancelRefundManager, + BatchDeleteRefundManager, + BatchConfirmRefundManager, + BatchCancelRefundManager, + + RefundDataService, + RefundReadService, + TransactionDataService, + + RefundDataOrchestrator, + RefundReadOrchestrator, + ], +}) +export class RefundModule {} diff --git a/src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper.ts b/src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper.ts new file mode 100644 index 0000000..b699e1c --- /dev/null +++ b/src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper.ts @@ -0,0 +1,131 @@ +import * as math from 'mathjs'; +import { Equation, parse } from 'algebra.js'; +import { HttpStatus, UnprocessableEntityException } from '@nestjs/common'; + +export function calculateSalesFormula( + formula: string, + taxes: object[], + total: number, + throwError = false, +) { + try { + let { value, variable, tax_datas } = mappingTaxes(taxes, formula, total); + + const x1 = math.simplify(formula, variable).toString(); + console.log('Formula ', x1); + const dppFormula = parse(x1); + const totalFormula = parse(total.toString()); + const equation = new Equation(totalFormula, dppFormula); + + console.log(equation.toString()); + const result = equation.solveFor('dpp').toString(); + console.log(result, 'formula'); + + value = math.evaluate(result); + console.log(value, 'value'); + + return { + dpp_value: value, + tax_datas: tax_datas, + }; + } catch (e) { + returnError(throwError, e, taxes); + } +} + +export function calculateProfitFormula( + formula: string, + taxes: object[], + total: number, + profit_share = 0, + throwError = false, +) { + try { + let { value, variable, tax_datas } = mappingTaxes( + taxes, + formula, + total, + profit_share, + ); + + const result = math.simplify(formula, variable).toString(); + console.log(result, 'formula'); + + value = math.evaluate(result); + console.log(value, 'value'); + + return { + dpp_value: value, + tax_datas: tax_datas, + }; + } catch (e) { + returnError(throwError, e, taxes); + } +} + +function mappingTaxes(taxes, formula, total, profit_share = 0) { + let value = 0; + const variable = {}; + let tax_datas = []; + const const_variable = ['profit_share', 'item_share', 'dpp']; + + const regex = /([a-zA-Z0-9_]+)/g; + + const matches: string[] = formula.match(regex); + const uniqueMatches = new Set(matches); + const keys = Array.from(uniqueMatches); + + for (const key of keys) { + if (!const_variable.includes(key)) { + const keyData = taxes.find((tax) => tax.name == key); + variable[key] = keyData.value / 100; + + tax_datas.push({ + tax_id: keyData.id, + tax_name: keyData.name, + tax_value: keyData.value, + tax_total_value: (keyData.value / 100) * Number(total), + }); + } else { + switch (key) { + case 'profit_share': + variable[key] = profit_share / 100; + break; + + case 'item_share': + variable[key] = profit_share / 100; + break; + + case 'dpp': + if (profit_share > 0) variable[key] = total; + break; + default: + variable[key] = profit_share; + break; + } + } + } + + return { + value: value, + variable: variable, + tax_datas: tax_datas, + }; +} + +function returnError(throwError, e, taxes) { + if (throwError) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! Formula error`, + error: 'Unprocessable Entity', + }); + } else { + console.log(e); + + return { + dpp_value: 0, + tax_datas: taxes, + }; + } +} diff --git a/src/modules/transaction/sales-price-formula/domain/usecases/managers/update-sales-price-formula.manager.ts b/src/modules/transaction/sales-price-formula/domain/usecases/managers/update-sales-price-formula.manager.ts index 89d24ee..1849e7a 100644 --- a/src/modules/transaction/sales-price-formula/domain/usecases/managers/update-sales-price-formula.manager.ts +++ b/src/modules/transaction/sales-price-formula/domain/usecases/managers/update-sales-price-formula.manager.ts @@ -8,10 +8,20 @@ import { columnUniques, validateRelations, } from 'src/core/strings/constants/interface.constants'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { In } from 'typeorm'; +import { calculateSalesFormula } from './helpers/calculation-formula.helper'; @Injectable() export class UpdateSalesPriceFormulaManager extends BaseUpdateManager { async validateProcess(): Promise { + const taxes = await this.dataServiceFirstOpt.getManyByOptions({ + where: { + status: In([STATUS.ACTIVE]), + }, + }); + + calculateSalesFormula(this.data.formula_string, taxes, 10000, true); return; } diff --git a/src/modules/transaction/sales-price-formula/domain/usecases/sales-price-formula-data.orchestrator.ts b/src/modules/transaction/sales-price-formula/domain/usecases/sales-price-formula-data.orchestrator.ts index 3b17c87..70408d4 100644 --- a/src/modules/transaction/sales-price-formula/domain/usecases/sales-price-formula-data.orchestrator.ts +++ b/src/modules/transaction/sales-price-formula/domain/usecases/sales-price-formula-data.orchestrator.ts @@ -4,12 +4,14 @@ import { SalesPriceFormulaEntity } from '../entities/sales-price-formula.entity' import { UpdateSalesPriceFormulaManager } from './managers/update-sales-price-formula.manager'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { FormulaType } from '../../constants'; +import { TaxDataService } from 'src/modules/transaction/tax/data/services/tax-data.service'; @Injectable() export class SalesPriceFormulaDataOrchestrator { constructor( private updateManager: UpdateSalesPriceFormulaManager, private serviceData: SalesPriceFormulaDataService, + private taxService: TaxDataService, ) {} async update(data): Promise { @@ -20,7 +22,11 @@ export class SalesPriceFormulaDataOrchestrator { }); this.updateManager.setData(formula.id, data); - this.updateManager.setService(this.serviceData, TABLE_NAME.PRICE_FORMULA); + this.updateManager.setService( + this.serviceData, + TABLE_NAME.PRICE_FORMULA, + this.taxService, + ); await this.updateManager.execute(); return this.updateManager.getResult(); } 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 a6e803f..accd83c 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 @@ -12,12 +12,17 @@ import { CqrsModule } from '@nestjs/cqrs'; import { UpdateSalesPriceFormulaManager } from './domain/usecases/managers/update-sales-price-formula.manager'; import { DetailSalesPriceFormulaManager } from './domain/usecases/managers/detail-sales-price-formula.manager'; import { SalesPriceFormulaModel } from './data/models/sales-price-formula.model'; +import { TaxDataService } from '../tax/data/services/tax-data.service'; +import { TaxModel } from '../tax/data/models/tax.model'; @Global() @Module({ imports: [ ConfigModule.forRoot(), - TypeOrmModule.forFeature([SalesPriceFormulaModel], CONNECTION_NAME.DEFAULT), + TypeOrmModule.forFeature( + [SalesPriceFormulaModel, TaxModel], + CONNECTION_NAME.DEFAULT, + ), CqrsModule, ], controllers: [ @@ -28,6 +33,7 @@ import { SalesPriceFormulaModel } from './data/models/sales-price-formula.model' DetailSalesPriceFormulaManager, UpdateSalesPriceFormulaManager, + TaxDataService, SalesPriceFormulaDataService, SalesPriceFormulaReadService, diff --git a/src/modules/transaction/tax/domain/usecases/tax-data.orchestrator.ts b/src/modules/transaction/tax/domain/usecases/tax-data.orchestrator.ts index c94cffb..d37d615 100644 --- a/src/modules/transaction/tax/domain/usecases/tax-data.orchestrator.ts +++ b/src/modules/transaction/tax/domain/usecases/tax-data.orchestrator.ts @@ -50,7 +50,7 @@ export class TaxDataOrchestrator extends BaseDataTransactionOrchestrator { + async delete(dataId): Promise { this.deleteManager.setData(dataId); this.deleteManager.setService( this.serviceData, @@ -72,7 +72,7 @@ export class TaxDataOrchestrator extends BaseDataTransactionOrchestrator { + async active(dataId): Promise { this.activeManager.setData(dataId, STATUS.ACTIVE); this.activeManager.setService(this.serviceData, TABLE_NAME.TAX); await this.activeManager.execute(); @@ -86,7 +86,7 @@ export class TaxDataOrchestrator extends BaseDataTransactionOrchestrator { + async confirm(dataId): Promise { this.confirmManager.setData(dataId, STATUS.ACTIVE); this.confirmManager.setService(this.serviceData, TABLE_NAME.TAX); await this.confirmManager.execute(); @@ -100,7 +100,7 @@ export class TaxDataOrchestrator extends BaseDataTransactionOrchestrator { + async inactive(dataId): Promise { this.inactiveManager.setData(dataId, STATUS.INACTIVE); this.inactiveManager.setService( this.serviceData, diff --git a/src/modules/transaction/tax/infrastructure/tax-data.controller.ts b/src/modules/transaction/tax/infrastructure/tax-data.controller.ts index ff6e2d0..283c9da 100644 --- a/src/modules/transaction/tax/infrastructure/tax-data.controller.ts +++ b/src/modules/transaction/tax/infrastructure/tax-data.controller.ts @@ -34,7 +34,7 @@ export class TaxDataController { } @Patch(':id/active') - async active(@Param('id') dataId: string): Promise { + async active(@Param('id') dataId: string): Promise { return await this.orchestrator.active(dataId); } @@ -44,7 +44,7 @@ export class TaxDataController { } @Patch(':id/confirm') - async confirm(@Param('id') dataId: string): Promise { + async confirm(@Param('id') dataId: string): Promise { return await this.orchestrator.confirm(dataId); } @@ -54,7 +54,7 @@ export class TaxDataController { } @Patch(':id/inactive') - async inactive(@Param('id') dataId: string): Promise { + async inactive(@Param('id') dataId: string): Promise { return await this.orchestrator.inactive(dataId); } @@ -72,7 +72,7 @@ export class TaxDataController { } @Delete(':id') - async delete(@Param('id') dataId: string): Promise { + async delete(@Param('id') dataId: string): Promise { return await this.orchestrator.delete(dataId); } } diff --git a/src/modules/transaction/transaction/constants.ts b/src/modules/transaction/transaction/constants.ts new file mode 100644 index 0000000..c03e7de --- /dev/null +++ b/src/modules/transaction/transaction/constants.ts @@ -0,0 +1,31 @@ +import { TransactionItemModel } from './data/models/transaction-item.model'; +import { TransactionTaxModel } from './data/models/transaction-tax.model'; +import { TransactionModel } from './data/models/transaction.model'; + +export enum TransactionPaymentType { + MIDTRANS = 'midtrans', + BANK_TRANSFER = 'bank transfer', + QRIS = 'qris', + COUNTER = 'counter', + CASH = 'cash', + CC = 'credit card', + DEBIT = 'debit', + EMONEY = 'e-money', +} + +export enum TransactionType { + COUNTER = 'counter', // transaksi yang dibuat dari POS / Counter + ADMIN = 'admin', // transaksi yang dibuat dari ADMIN page booking + ONLINE = 'online', // transaksi yang dibuat dari USER booking online +} + +export enum TransactionUserType { + GROUP = 'group', + VIP = 'vip', +} + +export const TransactionModels = [ + TransactionModel, + TransactionItemModel, + TransactionTaxModel, +]; diff --git a/src/modules/transaction/transaction/data/models/transaction-item.model.ts b/src/modules/transaction/transaction/data/models/transaction-item.model.ts new file mode 100644 index 0000000..d00e800 --- /dev/null +++ b/src/modules/transaction/transaction/data/models/transaction-item.model.ts @@ -0,0 +1,86 @@ +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { Column, Entity, JoinColumn, ManyToOne, OneToOne } from 'typeorm'; +import { BaseCoreModel } from 'src/core/modules/data/model/base-core.model'; +import { TransactionItemEntity } from '../../domain/entities/transaction-item.entity'; +import { TransactionModel } from './transaction.model'; +import { RefundItemModel } from 'src/modules/transaction/refund/data/models/refund-item.model'; + +@Entity(TABLE_NAME.TRANSACTION_ITEM) +export class TransactionItemModel + extends BaseCoreModel + implements TransactionItemEntity +{ + // item data + @Column('varchar', { name: 'item_id', nullable: true }) + item_id: string; + + @Column('varchar', { name: 'item_name', nullable: true }) + item_name: string; + + @Column('varchar', { name: 'item_type', nullable: true }) + item_type: string; + + @Column('bigint', { name: 'item_price', nullable: true }) + item_price: number; + + @Column('bigint', { name: 'item_hpp', nullable: true }) + item_hpp: number; + + @Column('varchar', { name: 'item_category_id', nullable: true }) + item_category_id: string; + + @Column('varchar', { name: 'item_category_name', nullable: true }) + item_category_name: string; + + @Column('json', { name: 'item_bundlings', nullable: true }) + item_bundlings: string; + + // item tenant data + @Column('varchar', { name: 'item_tenant_id', nullable: true }) + item_tenant_id: string; + + @Column('varchar', { name: 'item_tenant_name', nullable: true }) + item_tenant_name: string; + + @Column('decimal', { name: 'item_tenant_share_margin', nullable: true }) + item_tenant_share_margin: number; + + // calculation data + @Column('decimal', { name: 'total_price', nullable: true }) + total_price: number; + + @Column('decimal', { name: 'total_hpp', nullable: true }) + total_hpp: number; + + @Column('decimal', { name: 'total_profit', nullable: true }) + total_profit: number; + + @Column('decimal', { name: 'total_share_tenant', nullable: true }) + total_share_tenant: number; + + @Column('int', { name: 'qty', nullable: true }) + qty: number; + + @Column('int', { name: 'qty_remaining', nullable: true }) + qty_remaining: number; + + @Column('json', { name: 'taxes', nullable: true }) + taxes: string; + + @Column('varchar', { name: 'transaction_id', nullable: true }) + transaction_id: string; + @ManyToOne(() => TransactionModel, (model) => model.items, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + @JoinColumn({ name: 'transaction_id' }) + transaction: TransactionModel; + + // relations to refund + @OneToOne(() => RefundItemModel, (model) => model.transaction_item, { + cascade: true, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + refund: RefundItemModel; +} diff --git a/src/modules/transaction/transaction/data/models/transaction-tax.model.ts b/src/modules/transaction/transaction/data/models/transaction-tax.model.ts new file mode 100644 index 0000000..853644d --- /dev/null +++ b/src/modules/transaction/transaction/data/models/transaction-tax.model.ts @@ -0,0 +1,32 @@ +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; +import { TransactionTaxEntity } from '../../domain/entities/transaction-tax.entity'; +import { BaseCoreModel } from 'src/core/modules/data/model/base-core.model'; +import { TransactionModel } from './transaction.model'; + +@Entity(TABLE_NAME.TRANSACTION_TAX) +export class TransactionTaxModel + extends BaseCoreModel + implements TransactionTaxEntity +{ + @Column('varchar', { name: 'tax_id', nullable: true }) + tax_id: string; + + @Column('varchar', { name: 'tax_name', nullable: true }) + tax_name: string; + + @Column('decimal', { name: 'taxt_value', nullable: true }) + taxt_value: number; + + @Column('decimal', { name: 'tax_total_value', nullable: true }) + tax_total_value: number; + + @Column('varchar', { name: 'transaction_id', nullable: true }) + transaction_id: string; + @ManyToOne(() => TransactionModel, (model) => model.taxes, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + @JoinColumn({ name: 'transaction_id' }) + transaction: TransactionModel; +} diff --git a/src/modules/transaction/transaction/data/models/transaction.model.ts b/src/modules/transaction/transaction/data/models/transaction.model.ts new file mode 100644 index 0000000..c181d7a --- /dev/null +++ b/src/modules/transaction/transaction/data/models/transaction.model.ts @@ -0,0 +1,232 @@ +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { TransactionEntity } from '../../domain/entities/transaction.entity'; +import { Column, Entity, OneToMany, OneToOne } from 'typeorm'; +import { BaseStatusModel } from 'src/core/modules/data/model/base-status.model'; +import { + TransactionType, + TransactionUserType, + TransactionPaymentType, +} from '../../constants'; +import { TransactionItemEntity } from '../../domain/entities/transaction-item.entity'; +import { TransactionItemModel } from './transaction-item.model'; +import { TransactionTaxModel } from './transaction-tax.model'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { RefundModel } from 'src/modules/transaction/refund/data/models/refund.model'; + +@Entity(TABLE_NAME.TRANSACTION) +export class TransactionModel + extends BaseStatusModel + implements TransactionEntity +{ + // general info + @Column('bool', { name: 'is_recap_transaction', default: false }) + is_recap_transaction: boolean; + + @Column('enum', { + name: 'type', + enum: TransactionType, + default: TransactionType.ADMIN, + }) + type: TransactionType; + + @Column('varchar', { name: 'invoice_code', nullable: true }) + invoice_code: string; + + @Column('int', { name: 'creator_counter_no', nullable: true }) + creator_counter_no: number; + + // season data + @Column('varchar', { name: 'season_period_id', nullable: true }) + season_period_id: string; + + @Column('varchar', { name: 'season_period_name', nullable: true }) + season_period_name: string; + + @Column('varchar', { name: 'season_period_type_id', nullable: true }) + season_period_type_id: string; + + @Column('varchar', { name: 'season_period_type_name', nullable: true }) + season_period_type_name: string; + + // customer info + @Column('enum', { + name: 'customer_type', + enum: TransactionUserType, + nullable: true, + }) + customer_type: TransactionUserType; + + @Column('varchar', { name: 'customer_category_id', nullable: true }) + customer_category_id: string; + + @Column('varchar', { name: 'customer_category_name', nullable: true }) + customer_category_name: string; + + @Column('varchar', { name: 'customer_name', nullable: true }) + customer_name: string; + + @Column('varchar', { name: 'customer_phone', nullable: true }) + customer_phone: string; + + @Column('varchar', { name: 'customer_email', nullable: true }) + customer_email: string; + + @Column('varchar', { name: 'customer_description', nullable: true }) + customer_description: string; + + @Column('varchar', { name: 'no_of_group', nullable: true }) + no_of_group: number; + + @Column('date', { name: 'booking_date', nullable: true }) + booking_date: Date; + + @Column('date', { name: 'settlement_date', nullable: true }) + settlement_date: Date; + + @Column('date', { name: 'invoice_date', nullable: true }) + invoice_date: Date; + + // discount data + @Column('varchar', { name: 'discount_code_id', nullable: true }) + discount_code_id: string; + + @Column('varchar', { name: 'discount_code', nullable: true }) + discount_code: string; + + @Column('decimal', { name: 'discount_percentage', nullable: true }) + discount_percentage: number; + + @Column('decimal', { name: 'discount_value', nullable: true }) + discount_value: number; + + // payment data + @Column('enum', { + name: 'payment_type', + enum: TransactionPaymentType, + default: TransactionPaymentType.BANK_TRANSFER, + }) + payment_type: TransactionPaymentType; + + @Column('varchar', { name: 'payment_type_method_id', nullable: true }) + payment_type_method_id: string; + + @Column('varchar', { name: 'payment_type_method_name', nullable: true }) + payment_type_method_name: string; + + @Column('varchar', { name: 'payment_type_method_number', nullable: true }) + payment_type_method_number: string; + + @Column('varchar', { name: 'payment_type_method_qr', nullable: true }) + payment_type_method_qr: string; + + @Column('varchar', { name: 'payment_card_information', nullable: true }) + payment_card_information: string; + + @Column('varchar', { name: 'payment_code_reference', nullable: true }) + payment_code_reference: string; + + @Column('varchar', { name: 'payment_midtrans_token', nullable: true }) + payment_midtrans_token: string; + + @Column('varchar', { name: 'payment_midtrans_url', nullable: true }) + payment_midtrans_url: string; + + @Column('date', { name: 'payment_date', nullable: true }) + payment_date: Date; + + // calculation data + @Column('decimal', { name: 'payment_sub_total', nullable: true }) + payment_sub_total: number; + + @Column('decimal', { name: 'payment_discount_total', nullable: true }) + payment_discount_total: number; + + @Column('decimal', { name: 'payment_total', nullable: true }) + payment_total: number; + + @Column('decimal', { name: 'payment_total_pay', nullable: true }) + payment_total_pay: number; + + @Column('decimal', { name: 'payment_change', nullable: true }) + payment_change: number; + + // share and profit data + @Column('decimal', { name: 'payment_total_dpp', nullable: true }) + payment_total_dpp: number; + + @Column('decimal', { name: 'payment_total_share', nullable: true }) + payment_total_share: number; + + @Column('decimal', { name: 'payment_total_tax', nullable: true }) + payment_total_tax: number; + + @Column('decimal', { name: 'payment_total_profit', nullable: true }) + payment_total_profit: number; + + @Column('varchar', { name: 'profit_share_formula', nullable: true }) + profit_share_formula: string; + + @Column('varchar', { name: 'sales_price_formula', nullable: true }) + sales_price_formula: string; + + // mdr + @Column('decimal', { name: 'reconciliation_mdr', nullable: true }) + reconciliation_mdr: number; + + @Column('enum', { + name: 'reconciliation_status', + enum: STATUS, + default: STATUS.DRAFT, + }) + reconciliation_status: STATUS; + + @Column('varchar', { name: 'reconciliation_confirm_date', nullable: true }) + reconciliation_confirm_date: string; + + @Column('varchar', { name: 'reconciliation_confirm_by', nullable: true }) + reconciliation_confirm_by: string; + + @Column('decimal', { name: 'payment_total_net_profit', nullable: true }) + payment_total_net_profit: number; + + // sending data + @Column('enum', { + name: 'sending_invoice_status', + enum: STATUS, + nullable: true, + }) + sending_invoice_status: STATUS; + + @Column({ name: 'sending_invoice_at', type: 'bigint', nullable: true }) + sending_invoice_at: number; + + @Column('enum', { name: 'sending_qr_status', enum: STATUS, nullable: true }) + sending_qr_status: STATUS; + + @Column({ name: 'sending_qr_at', type: 'bigint', nullable: true }) + sending_qr_at: number; + + // relations to item + @OneToMany(() => TransactionItemModel, (model) => model.transaction, { + cascade: true, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + items: TransactionItemEntity[]; + + // relations to tax data + @OneToMany(() => TransactionTaxModel, (model) => model.transaction, { + cascade: true, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + taxes: TransactionTaxModel[]; + + // relations to refund + @OneToOne(() => RefundModel, (model) => model.transaction, { + cascade: true, + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + refund: RefundModel; +} diff --git a/src/modules/transaction/transaction/data/services/transaction-data.service.ts b/src/modules/transaction/transaction/data/services/transaction-data.service.ts new file mode 100644 index 0000000..f13c3b7 --- /dev/null +++ b/src/modules/transaction/transaction/data/services/transaction-data.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; +import { TransactionEntity } from '../../domain/entities/transaction.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { TransactionModel } from '../models/transaction.model'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { Repository } from 'typeorm'; + +@Injectable() +export class TransactionDataService extends BaseDataService { + constructor( + @InjectRepository(TransactionModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/transaction/transaction/data/services/transaction-read.service.ts b/src/modules/transaction/transaction/data/services/transaction-read.service.ts new file mode 100644 index 0000000..ddd488a --- /dev/null +++ b/src/modules/transaction/transaction/data/services/transaction-read.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { TransactionEntity } from '../../domain/entities/transaction.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { TransactionModel } from '../models/transaction.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 TransactionReadService extends BaseReadService { + constructor( + @InjectRepository(TransactionModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event.ts b/src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event.ts new file mode 100644 index 0000000..53ffa35 --- /dev/null +++ b/src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class TransactionChangeStatusEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/transaction/transaction/domain/entities/event/transaction-created.event.ts b/src/modules/transaction/transaction/domain/entities/event/transaction-created.event.ts new file mode 100644 index 0000000..2e02ca9 --- /dev/null +++ b/src/modules/transaction/transaction/domain/entities/event/transaction-created.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class TransactionCreatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/transaction/transaction/domain/entities/event/transaction-deleted.event.ts b/src/modules/transaction/transaction/domain/entities/event/transaction-deleted.event.ts new file mode 100644 index 0000000..db26934 --- /dev/null +++ b/src/modules/transaction/transaction/domain/entities/event/transaction-deleted.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class TransactionDeletedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/transaction/transaction/domain/entities/event/transaction-updated.event.ts b/src/modules/transaction/transaction/domain/entities/event/transaction-updated.event.ts new file mode 100644 index 0000000..e3a82c9 --- /dev/null +++ b/src/modules/transaction/transaction/domain/entities/event/transaction-updated.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class TransactionUpdatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/transaction/transaction/domain/entities/filter-transaction.entity.ts b/src/modules/transaction/transaction/domain/entities/filter-transaction.entity.ts new file mode 100644 index 0000000..8340ea6 --- /dev/null +++ b/src/modules/transaction/transaction/domain/entities/filter-transaction.entity.ts @@ -0,0 +1,38 @@ +import { BaseFilterEntity } from 'src/core/modules/domain/entities/base-filter.entity'; + +export interface FilterTransactionEntity extends BaseFilterEntity { + // search mdr + customer_names?: string[]; + confirm_by_names?: string[]; + payment_banks?: string[]; + payment_bank_numbers?: string[]; + + // seatch booking; + payment_types?: string[]; + types?: string[]; + customer_types?: string[]; + invoice_codes?: string[]; + refund_codes?: string[]; + creator_names?: string[]; + + // filter mdr + payment_date_from?: Date; + payment_date_to?: Date; + transaction_type?: string; + couner_no?: number; + payment_type?: string; + payment_via?: string; + payment_bank?: string; + confirmation_date_from?: Date; + confirmation_date_to?: Date; + + // filter booking + booking_date_from?: Date; + booking_date_to?: Date; + invoice_date_from?: Date; + invoice_date_to?: Date; + settlement_date_from?: Date; + settlement_date_to?: Date; + request_refund_date_from?: Date; + request_refund_date_to?: Date; +} diff --git a/src/modules/transaction/transaction/domain/entities/transaction-item.entity.ts b/src/modules/transaction/transaction/domain/entities/transaction-item.entity.ts new file mode 100644 index 0000000..102355a --- /dev/null +++ b/src/modules/transaction/transaction/domain/entities/transaction-item.entity.ts @@ -0,0 +1,27 @@ +import { BaseCoreEntity } from 'src/core/modules/domain/entities/base-core.entity'; + +export interface TransactionItemEntity extends BaseCoreEntity { + // item detail + item_id: string; + item_name: string; + item_type: string; + item_price: number; + item_hpp: number; + item_category_id: string; + item_category_name: string; + item_bundlings: string; + + // item tenant data + item_tenant_id: string; + item_tenant_name: string; + item_tenant_share_margin: number; + + // calculation data + total_price: number; + total_hpp: number; + total_profit: number; + total_share_tenant: number; + qty: number; + qty_remaining: number; + taxes: string; +} diff --git a/src/modules/transaction/transaction/domain/entities/transaction-tax.entity.ts b/src/modules/transaction/transaction/domain/entities/transaction-tax.entity.ts new file mode 100644 index 0000000..183853c --- /dev/null +++ b/src/modules/transaction/transaction/domain/entities/transaction-tax.entity.ts @@ -0,0 +1,8 @@ +import { BaseCoreEntity } from 'src/core/modules/domain/entities/base-core.entity'; + +export interface TransactionTaxEntity extends BaseCoreEntity { + tax_id: string; + tax_name: string; + taxt_value: number; + tax_total_value: number; +} diff --git a/src/modules/transaction/transaction/domain/entities/transaction.entity.ts b/src/modules/transaction/transaction/domain/entities/transaction.entity.ts new file mode 100644 index 0000000..09f1f54 --- /dev/null +++ b/src/modules/transaction/transaction/domain/entities/transaction.entity.ts @@ -0,0 +1,81 @@ +import { BaseStatusEntity } from 'src/core/modules/domain/entities/base-status.entity'; +import { + TransactionPaymentType, + TransactionType, + TransactionUserType, +} from '../../constants'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +export interface TransactionEntity extends BaseStatusEntity { + // general info + is_recap_transaction: boolean; + type: TransactionType; + invoice_code: string; + creator_counter_no: number; // nomor pos transaksi dibuat + + // season data + season_period_id: string; + season_period_name: string; + season_period_type_id: string; + season_period_type_name: string; + + // customer info + customer_category_id: string; + customer_category_name: string; + customer_type: TransactionUserType; + customer_name: string; + customer_phone: string; + customer_email: string; + customer_description: string; + no_of_group: number; + + booking_date: Date; // tnaggal untuk booking + settlement_date: Date; // tanggal status berubah menjadi settlement + invoice_date: Date; // tanggal invoice terkirim + + // discount data + discount_code_id: string; + discount_code: string; + discount_percentage: number; + discount_value: number; + + // payment data + payment_type: TransactionPaymentType; + payment_type_method_id: string; + payment_type_method_name: string; + payment_type_method_number: string; + payment_type_method_qr: string; + payment_card_information: string; + payment_code_reference: string; + payment_midtrans_token: string; + payment_midtrans_url: string; + payment_date: Date; + + // calculation data + payment_sub_total: number; // total invoice tanpa discount + payment_discount_total: number; // total discount + payment_total: number; // total invoice + payment_total_pay: number; // total pembayaran user + payment_change: number; // total kembalian + + // share and profit data + payment_total_share: number; // total share untuk para tenant + payment_total_tax: number; // total untuk tax + payment_total_profit: number; // total untuk profit perusahan + payment_total_dpp: number; + profit_share_formula: string; + sales_price_formula: string; + + // mdr data + reconciliation_mdr: number; + reconciliation_status: STATUS; + reconciliation_confirm_date: string; + reconciliation_confirm_by: string; + payment_total_net_profit: number; // net pendapatan + + // sending data + sending_invoice_at: number; + sending_invoice_status: STATUS; + sending_qr_at: number; + sending_qr_status: STATUS; +} diff --git a/src/modules/transaction/transaction/domain/usecases/handlers/midtrans-transaction-callback.handler.ts b/src/modules/transaction/transaction/domain/usecases/handlers/midtrans-transaction-callback.handler.ts new file mode 100644 index 0000000..ec0d46b --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/handlers/midtrans-transaction-callback.handler.ts @@ -0,0 +1,44 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { MidtransCallbackEvent } from 'src/modules/configuration/midtrans/domain/entities/midtrans-callback.event'; +import { TransactionDataService } from '../../../data/services/transaction-data.service'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@EventsHandler(MidtransCallbackEvent) +export class MidtransCallbackHandler + implements IEventHandler +{ + constructor(private dataService: TransactionDataService) {} + + async handle(event: MidtransCallbackEvent) { + const data_id = event.data.id; + const data = event.data.data; + const transaction = await this.dataService.getOneByOptions({ + where: { + id: data_id, + }, + }); + + if (['capture', 'settlement'].includes(data.transaction_status)) { + Object.assign(transaction, { + status: STATUS.SETTLED, + settlement_date: new Date(data.settlement_time), + payment_code_reference: data.approval_code, + }); + } else if (['pending'].includes(data['transaction_status'])) { + Object.assign(transaction, { + status: STATUS.PENDING, + }); + } else { + Object.assign(transaction, { + status: STATUS.EXPIRED, + }); + } + + const queryRunner = this.dataService + .getRepository() + .manager.connection.createQueryRunner(); + + await this.dataService.create(queryRunner, TransactionModel, transaction); + } +} 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 new file mode 100644 index 0000000..d79dc6e --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/handlers/pos-transaction.handler.ts @@ -0,0 +1,83 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { ChangeDocEvent } from 'src/modules/configuration/couch/domain/events/change-doc.event'; +import { TransactionType } from '../../../constants'; +import { TransactionDataService } from '../../../data/services/transaction-data.service'; +import { TaxDataService } from 'src/modules/transaction/tax/data/services/tax-data.service'; +import { SalesPriceFormulaDataService } from 'src/modules/transaction/sales-price-formula/data/services/sales-price-formula-data.service'; +import { FormulaType } from 'src/modules/transaction/sales-price-formula/constants'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { mappingRevertTransaction } from '../managers/helpers/mapping-transaction.helper'; + +@EventsHandler(ChangeDocEvent) +export class PosTransactionHandler implements IEventHandler { + constructor( + private dataService: TransactionDataService, + private taxService: TaxDataService, + private formulaService: SalesPriceFormulaDataService, + ) {} + + async handle(event: ChangeDocEvent) { + try { + const database = event.data.database; + const data = event.data.data; + + // jika bukan database transaksi, return langsung + if (database != 'transaction') return; + + const sales_formula = await this.formulaService.getOneByOptions({ + where: { + type: FormulaType.SALES_PRICE, + }, + }); + + const profit_formula = await this.formulaService.getOneByOptions({ + where: { + type: FormulaType.PROFIT_SHARE, + }, + }); + + const taxes = await this.taxService.getManyByOptions({ + where: { + status: STATUS.ACTIVE, + }, + }); + + const queryRunner = this.dataService + .getRepository() + .manager.connection.createQueryRunner(); + + // jika delete + if (data._deleted ?? false) { + await this.dataService.deleteById( + queryRunner, + TransactionModel, + data._id, + ); + } + + // jika update // create + else { + const tax_datas = taxes?.map((tax) => { + return { + tax_id: tax.id, + tax_name: tax.name, + tax_value: tax.value, + }; + }); + + mappingRevertTransaction(data, TransactionType.COUNTER); + + Object.assign(data, { + taxes: tax_datas, + profit_share_formula: profit_formula.formula_string, + sales_price_formula: sales_formula.formula_string, + }); + + await this.dataService.create(queryRunner, TransactionModel, data); + } + } catch (error) { + console.log('error handling pos transaction couch'); + } + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/handlers/refund-update.handler.ts b/src/modules/transaction/transaction/domain/usecases/handlers/refund-update.handler.ts new file mode 100644 index 0000000..7d15c32 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/handlers/refund-update.handler.ts @@ -0,0 +1,52 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { RefundChangeStatusEvent } from 'src/modules/transaction/refund/domain/entities/event/refund-change-status.event'; +import { TransactionDataService } from '../../../data/services/transaction-data.service'; +import { OPERATION, STATUS } from 'src/core/strings/constants/base.constants'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { RefundDeletedEvent } from 'src/modules/transaction/refund/domain/entities/event/refund-deleted.event'; + +@EventsHandler(RefundChangeStatusEvent, RefundDeletedEvent) +export class RefundUpdatedHandler + implements IEventHandler +{ + constructor(private dataService: TransactionDataService) {} + + async handle(event: RefundChangeStatusEvent) { + const old_data = event.data.old; + const current_data = event.data.data; + let status: STATUS; + + if ( + old_data.status != current_data.data || + (event.data.op == OPERATION.DELETE && current_data.status != STATUS.DRAFT) + ) { + const queryRunner = this.dataService + .getRepository() + .manager.connection.createQueryRunner(); + + const data = new TransactionModel(); + const if_full_refund = + Number(current_data.refund_total ?? 0) == + Number(current_data.transaction?.payment_total); + + if (event.data.op == OPERATION.DELETE) status = STATUS.SETTLED; + else if (current_data.status == STATUS.PENDING) + status = STATUS.PROCESS_REFUND; + else if (current_data.status == STATUS.REFUNDED && if_full_refund) + status = STATUS.REFUNDED; + else if (current_data.status == STATUS.REFUNDED && !if_full_refund) + status = STATUS.PARTIAL_REFUND; + else if (current_data.status == STATUS.CANCEL) status = STATUS.SETTLED; + + await this.dataService.update( + queryRunner, + TransactionModel, + { id: current_data.transaction_id }, + { + ...data, + status: status, + }, + ); + } + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/handlers/settled-transaction.handler.ts b/src/modules/transaction/transaction/domain/usecases/handlers/settled-transaction.handler.ts new file mode 100644 index 0000000..f26b6b5 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/handlers/settled-transaction.handler.ts @@ -0,0 +1,76 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event'; +import { SalesPriceFormulaDataService } from 'src/modules/transaction/sales-price-formula/data/services/sales-price-formula-data.service'; +import { TaxDataService } from 'src/modules/transaction/tax/data/services/tax-data.service'; +import { FormulaType } from 'src/modules/transaction/sales-price-formula/constants'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { TransactionDataService } from '../../../data/services/transaction-data.service'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { calculateSalesFormula } from 'src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper'; + +@EventsHandler(TransactionChangeStatusEvent) +export class SettledTransactionHandler + implements IEventHandler +{ + constructor( + private formulaService: SalesPriceFormulaDataService, + private taxService: TaxDataService, + private dataService: TransactionDataService, + ) {} + + async handle(event: TransactionChangeStatusEvent) { + const old_data = event.data.old; + const current_data = event.data.data; + + if ( + old_data.status == current_data.status || + ![STATUS.ACTIVE, STATUS.SETTLED].includes(current_data.status) + ) + return; + + const data = await this.dataService.getOneByOptions({ + where: { + id: current_data.id, + }, + relations: ['items'], + }); + + const profit_formula = await this.formulaService.getOneByOptions({ + where: { + type: FormulaType.PROFIT_SHARE, + }, + }); + + const sales_price = await this.formulaService.getOneByOptions({ + where: { + type: FormulaType.SALES_PRICE, + }, + }); + + const taxes = await this.taxService.getManyByOptions({ + where: { + status: STATUS.ACTIVE, + }, + }); + + const queryRunner = this.dataService + .getRepository() + .manager.connection.createQueryRunner(); + + // const profit_share_value = this.calculateFormula(profit_formula.formula_string, taxes, data.payment_total_net_profit ?? 0); + const { dpp_value, tax_datas } = calculateSalesFormula( + sales_price.formula_string, + taxes, + data.payment_total_net_profit ?? 0, + ); + + Object.assign(data, { + payment_total_dpp: dpp_value, + profit_share_formula: profit_formula.formula_string, + sales_price_formula: sales_price.formula_string, + taxes: tax_datas, + }); + + await this.dataService.create(queryRunner, TransactionModel, data); + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/active-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/active-transaction.manager.ts new file mode 100644 index 0000000..8287849 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/active-transaction.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 { TransactionEntity } from '../../entities/transaction.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event'; + +@Injectable() +export class ActiveTransactionManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.invoice_code}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TransactionChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/batch-active-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/batch-active-transaction.manager.ts new file mode 100644 index 0000000..48ad15c --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/batch-active-transaction.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { TransactionEntity } from '../../entities/transaction.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchActiveTransactionManager extends BaseBatchUpdateStatusManager { + validateData(data: TransactionEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TransactionChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/batch-cancel-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/batch-cancel-transaction.manager.ts new file mode 100644 index 0000000..4fb4157 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/batch-cancel-transaction.manager.ts @@ -0,0 +1,58 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { TransactionEntity } from '../../entities/transaction.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class BatchCancelTransactionManager extends BaseBatchUpdateStatusManager { + validateData(data: TransactionEntity): Promise { + if (![STATUS.EXPIRED, STATUS.PENDING].includes(data.status)) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only data booking with status ${STATUS.ACTIVE} can be confirm`, + error: 'Unprocessable Entity', + }); + } + + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TransactionChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/batch-confirm-data-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/batch-confirm-data-transaction.manager.ts new file mode 100644 index 0000000..d3e593a --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/batch-confirm-data-transaction.manager.ts @@ -0,0 +1,71 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { TransactionEntity } from '../../entities/transaction.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { TransactionPaymentType } from '../../../constants'; + +@Injectable() +export class BatchConfirmDataTransactionManager extends BaseBatchUpdateStatusManager { + validateData(data: TransactionEntity): Promise { + if ( + ![STATUS.PENDING, STATUS.REJECTED, STATUS.EXPIRED].includes(data.status) + ) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only data booking with status ${STATUS.PENDING}, ${STATUS.REJECTED}, ${STATUS.EXPIRED} can be confirm`, + error: 'Unprocessable Entity', + }); + } + + Object.assign(data, { + status: STATUS.WAITING, + reconciliation_status: [ + TransactionPaymentType.COUNTER, + TransactionPaymentType.MIDTRANS, + ].includes(data.payment_type) + ? null + : STATUS.PENDING, + }); + + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TransactionChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/batch-confirm-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/batch-confirm-transaction.manager.ts new file mode 100644 index 0000000..47606d4 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/batch-confirm-transaction.manager.ts @@ -0,0 +1,85 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { TransactionEntity } from '../../entities/transaction.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { generateInvoiceCodeHelper } from './helpers/generate-invoice-code.helper'; +import { TransactionPaymentType } from '../../../constants'; + +@Injectable() +export class BatchConfirmTransactionManager extends BaseBatchUpdateStatusManager { + protected relations = ['items']; + + async validateData(data: TransactionEntity): Promise { + if (data.status != STATUS.DRAFT) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only data booking with status ${STATUS.ACTIVE} can be confirm`, + error: 'Unprocessable Entity', + }); + } + + const freeTransaction = data.payment_total < 1; + if (data.payment_type == TransactionPaymentType.MIDTRANS) { + try { + const { token, redirect_url } = await this.dataServiceFirstOpt.create( + data, + ); + Object.assign(data, { + payment_midtrans_token: token, + payment_midtrans_url: redirect_url, + }); + } catch (error) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! this transaction already created, please check your email to continue payment`, + error: 'Unprocessable Entity', + }); + } + } + + Object.assign(data, { + invoice_code: await generateInvoiceCodeHelper(this.dataService), + status: freeTransaction ? STATUS.ACTIVE : STATUS.PENDING, + }); + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TransactionChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/batch-delete-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/batch-delete-transaction.manager.ts new file mode 100644 index 0000000..9e7510e --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/batch-delete-transaction.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchDeleteManager } from 'src/core/modules/domain/usecase/managers/base-batch-delete.manager'; +import { TransactionEntity } from '../../entities/transaction.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { TransactionDeletedEvent } from '../../entities/event/transaction-deleted.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchDeleteTransactionManager extends BaseBatchDeleteManager { + async beforeProcess(): Promise { + return; + } + + async validateData(data: TransactionEntity): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TransactionDeletedEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/batch-inactive-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/batch-inactive-transaction.manager.ts new file mode 100644 index 0000000..010e827 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/batch-inactive-transaction.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { TransactionEntity } from '../../entities/transaction.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchInactiveTransactionManager extends BaseBatchUpdateStatusManager { + validateData(data: TransactionEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TransactionChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/cancel-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/cancel-transaction.manager.ts new file mode 100644 index 0000000..2e939e3 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/cancel-transaction.manager.ts @@ -0,0 +1,61 @@ +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager'; +import { TransactionEntity } from '../../entities/transaction.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class CancelTransactionManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.invoice_code}`; + } + + async validateProcess(): Promise { + if (![STATUS.EXPIRED, STATUS.PENDING].includes(this.data.status)) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only data booking with status ${STATUS.ACTIVE} can be confirm`, + error: 'Unprocessable Entity', + }); + } + return; + } + + async beforeProcess(): Promise { + const freeTransaction = this.data.payment_total < 1; + Object.assign(this.data, { + status: freeTransaction ? STATUS.ACTIVE : STATUS.PENDING, + }); + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TransactionChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/confirm-data-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/confirm-data-transaction.manager.ts new file mode 100644 index 0000000..f860f9e --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/confirm-data-transaction.manager.ts @@ -0,0 +1,72 @@ +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager'; +import { TransactionEntity } from '../../entities/transaction.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { TransactionPaymentType } from '../../../constants'; + +@Injectable() +export class ConfirmDataTransactionManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.invoice_code}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + const old_status = this.oldData.status; + + if ( + ![STATUS.PENDING, STATUS.REJECTED, STATUS.EXPIRED].includes(old_status) + ) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only data booking with status ${STATUS.PENDING}, ${STATUS.REJECTED}, ${STATUS.EXPIRED} can be confirm`, + error: 'Unprocessable Entity', + }); + } + + Object.assign(this.data, { + status: STATUS.WAITING, + reconciliation_status: [ + TransactionPaymentType.COUNTER, + TransactionPaymentType.MIDTRANS, + ].includes(this.oldData.payment_type) + ? null + : STATUS.PENDING, + }); + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TransactionChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/confirm-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/confirm-transaction.manager.ts new file mode 100644 index 0000000..32be4b0 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/confirm-transaction.manager.ts @@ -0,0 +1,88 @@ +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { BaseUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-update-status.manager'; +import { TransactionEntity } from '../../entities/transaction.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { generateInvoiceCodeHelper } from './helpers/generate-invoice-code.helper'; +import { TransactionPaymentType } from '../../../constants'; + +@Injectable() +export class ConfirmTransactionManager extends BaseUpdateStatusManager { + protected relations = ['items']; + + getResult(): string { + return `Success active data ${this.result.invoice_code}`; + } + + async validateProcess(): Promise { + if (this.oldData.status != STATUS.DRAFT) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! only data booking with status ${STATUS.ACTIVE} can be confirm`, + error: 'Unprocessable Entity', + }); + } + return; + } + + async beforeProcess(): Promise { + const freeTransaction = this.data.payment_total < 1; + + if (this.data.payment_type == TransactionPaymentType.MIDTRANS) { + try { + const { token, redirect_url } = await this.dataServiceFirstOpt.create( + this.oldData, + ); + Object.assign(this.data, { + payment_midtrans_token: token, + payment_midtrans_url: redirect_url, + }); + } catch (error) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! this transaction already created, please check your email to continue payment`, + error: 'Unprocessable Entity', + }); + } + } + + Object.assign(this.data, { + invoice_code: + this.data.payment_type == TransactionPaymentType.COUNTER + ? null + : await generateInvoiceCodeHelper(this.dataService), + status: freeTransaction ? STATUS.ACTIVE : STATUS.PENDING, + }); + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TransactionChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/create-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/create-transaction.manager.ts new file mode 100644 index 0000000..9f45062 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/create-transaction.manager.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TransactionEntity } from '../../entities/transaction.entity'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager'; +import { TransactionCreatedEvent } from '../../entities/event/transaction-created.event'; +import { TransactionType } from '../../../constants'; +import { mappingRevertTransaction } from './helpers/mapping-transaction.helper'; + +@Injectable() +export class CreateTransactionManager extends BaseCreateManager { + async beforeProcess(): Promise { + mappingRevertTransaction(this.data, TransactionType.ADMIN); + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TransactionCreatedEvent, + data: this.data, + }, + ]; + } + + get entityTarget(): any { + return TransactionModel; + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/delete-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/delete-transaction.manager.ts new file mode 100644 index 0000000..de6e3e6 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/delete-transaction.manager.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDeleteManager } from 'src/core/modules/domain/usecase/managers/base-delete.manager'; +import { TransactionEntity } from '../../entities/transaction.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { TransactionDeletedEvent } from '../../entities/event/transaction-deleted.event'; + +@Injectable() +export class DeleteTransactionManager 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 TransactionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TransactionDeletedEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/detail-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/detail-transaction.manager.ts new file mode 100644 index 0000000..729c6ce --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/detail-transaction.manager.ts @@ -0,0 +1,88 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDetailManager } from 'src/core/modules/domain/usecase/managers/base-detail.manager'; +import { TransactionEntity } from '../../entities/transaction.entity'; +import { RelationParam } from 'src/core/modules/domain/entities/base-filter.entity'; +import { mappingTransaction } from './helpers/mapping-transaction.helper'; + +@Injectable() +export class DetailTransactionManager extends BaseDetailManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + mappingTransaction(this.result); + return; + } + + get relations(): RelationParam { + return { + // relation only join (for query purpose) + joinRelations: [], + + // relation join and select (relasi yang ingin ditampilkan), + selectRelations: ['items', 'items.refund item_refund', 'refund'], + + // relation yang hanya ingin dihitung (akan return number) + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.creator_counter_no`, + `${this.tableName}.creator_name`, + `${this.tableName}.created_at`, + `${this.tableName}.updated_at`, + `${this.tableName}.editor_name`, + `${this.tableName}.invoice_code`, + `${this.tableName}.invoice_date`, + `${this.tableName}.settlement_date`, + + `${this.tableName}.season_period_id`, + `${this.tableName}.season_period_name`, + `${this.tableName}.season_period_type_id`, + `${this.tableName}.season_period_type_name`, + + `${this.tableName}.status`, + `${this.tableName}.no_of_group`, + `${this.tableName}.customer_type`, + `${this.tableName}.customer_name`, + `${this.tableName}.customer_phone`, + `${this.tableName}.customer_email`, + `${this.tableName}.customer_description`, + `${this.tableName}.booking_date`, + + `${this.tableName}.discount_percentage`, + `${this.tableName}.discount_value`, + + `${this.tableName}.payment_type`, + `${this.tableName}.payment_date`, + `${this.tableName}.payment_total_pay`, + `${this.tableName}.payment_type_method_id`, + `${this.tableName}.payment_type_method_name`, + `${this.tableName}.payment_type_method_number`, + `${this.tableName}.payment_card_information`, + `${this.tableName}.payment_code_reference`, + + `${this.tableName}.payment_sub_total`, + `${this.tableName}.payment_discount_total`, + `${this.tableName}.payment_total`, + + 'items', + 'item_refund', + 'refund', + ]; + } + + get setFindProperties(): any { + return { + id: this.dataId, + }; + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/helpers/generate-invoice-code.helper.ts b/src/modules/transaction/transaction/domain/usecases/managers/helpers/generate-invoice-code.helper.ts new file mode 100644 index 0000000..93b9ba3 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/helpers/generate-invoice-code.helper.ts @@ -0,0 +1,22 @@ +import { + generateCodeDate, + generateRandom, +} from 'src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper'; +import { ILike } from 'typeorm'; + +export async function generateInvoiceCodeHelper(dataService) { + const month_year = generateCodeDate(); + const char = generateRandom(1); + const number = generateRandom(1, true); + + const invoice_code = await dataService.getManyByOptions({ + where: { + invoice_code: ILike(`%${month_year}%`), + }, + }); + const current_number = invoice_code.length + 1; + + return `INV-${month_year}${char}${number}/${current_number + .toString() + .padStart(5, '0')}`; +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/helpers/mapping-transaction.helper.ts b/src/modules/transaction/transaction/domain/usecases/managers/helpers/mapping-transaction.helper.ts new file mode 100644 index 0000000..7f5e6d0 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/helpers/mapping-transaction.helper.ts @@ -0,0 +1,168 @@ +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { + TransactionPaymentType, + TransactionType, +} from 'src/modules/transaction/transaction/constants'; + +export function mappingTransaction(data) { + let payment_type_bank: any = null; + const season_period = { + id: data.season_period_id, + holiday_name: data.season_period_name, + season_type: { + id: data.season_period_type_id, + name: data.season_period_type_name, + }, + }; + + if (data.payment_type_method_id || data.payment_type_method_name) { + payment_type_bank = { + id: data.payment_type_method_id, + issuer_name: data.payment_type_method_name, + account_number: data.payment_type_method_number, + qr_image: data.payment_type_method_qr, + }; + } + + const items = data?.['items']?.map((itemData) => { + let tenant; + + if (itemData.item_tenant_id) { + tenant = { + id: itemData.item_tenant_id, + name: itemData.item_tenant_name, + share_margin: itemData.item_tenant_share_margin, + }; + } + + return { + item: { + id: itemData.item_id, + name: itemData.item_name, + item_type: itemData.item_type, + base_price: itemData.item_price, + hpp: itemData.item_hpp, + tenant: tenant, + item_category: { + id: itemData.item_category_id, + name: itemData.item_category_name, + }, + }, + id: itemData.id, + refund: itemData.refund, + qty: itemData.qty, + qty_remaining: itemData.qty_remaining, + total_price_refund: itemData.refund?.refund_total ?? 0, + total_price: itemData.total_price, + }; + }); + + Object.assign(data, { + season_period: season_period, + items: items, + payment_type_bank: payment_type_bank, + }); + + delete data.season_period_id; + delete data.season_period_name; + delete data.season_period_type_id; + delete data.season_period_type_name; + + delete data.payment_type_method_id; + delete data.payment_type_method_name; + delete data.payment_type_method_number; + delete data.payment_type_method_qr; +} + +export function mappingRevertTransaction(data, type) { + if (type == TransactionType.COUNTER) { + if (data.booking_id) { + Object.assign(data, { + editor_id: data.pos_admin?.id, + editor_name: data.pos_admin?.name, + edited_at: new Date(data.created_at), + }); + } else { + Object.assign(data, { + creator_id: data.pos_admin?.id, + creator_name: data.pos_admin?.name, + }); + } + + Object.assign(data, { + id: data.booking_id ?? data._id, + invoice_code: data.code, + creator_counter_no: Number(data.pos_number), + status: STATUS.SETTLED, + settlement_date: new Date(data.created_at), + payment_date: new Date(data.created_at), + invoice_date: new Date(data.created_at), + payment_type: + data.payment_type == 'cc' + ? TransactionPaymentType.CC + : data.payment_type, + payment_card_information: data.card_information, + payment_code_reference: data.payment_code, + discount_code_id: data.discount_code?.id, + discount_code: data.discount_code?.code, + discount_percentage: data.discount_code?.discount, + }); + } else { + // Object.assign(data, { + // payment_type: + // }) + } + + Object.assign(data, { + type: type, + payment_total_net_profit: data.payment_total, + customer_category_id: data.customer_category?.id ?? null, + customer_category_name: data.customer_category?.name ?? null, + season_period_id: data.season_period?.id ?? null, + season_period_name: data.season_period?.holiday_name ?? null, + season_period_type_id: data.season_period?.season_type?.id ?? null, + season_period_type_name: data.season_period?.season_type?.name ?? null, + payment_type_method_id: + data.payment_type_method?.id ?? data.payment_type_bank?.id, + payment_type_method_number: + data.payment_type_method?.account_number ?? + data.payment_type_bank?.account_number, + payment_type_method_name: + data.payment_type_method?.issuer_name ?? + data.payment_type_bank?.issuer_name, + payment_type_method_qr: + data.payment_type_method?.qr_image ?? data.payment_type_bank?.qr_image, + }); + + data.items?.map((item) => { + const total_price = + Number(item.item.price ?? item.item.base_price) * Number(item.qty); + const share_margin = item.item.tenant?.share_margin ?? 0; + const total_share_tenant = + share_margin > 0 ? (Number(share_margin) / 100) * total_price : 0; + + Object.assign(item, { + item_id: item.item.id, + item_name: item.item.name, + item_type: item.item.item_type, + item_price: item.item.base_price, + item_hpp: item.item.hpp, + qty_remaining: item.qty, + + item_category_id: item.item.item_category?.id, + item_category_name: item.item.item_category?.name, + item_bundlings: item.item.bundling_items?.map( + (bundling) => bundling.name, + ), + + item_tenant_id: item.item.tenant?.id ?? null, + item_tenant_name: item.item.tenant?.id ?? null, + item_tenant_share_margin: item.item.tenant?.share_margin ?? null, + + total_price: total_price, + total_hpp: Number(item.item.item_hpp) * Number(item.qty), + total_share_tenant: total_share_tenant, + total_profit: total_price - Number(total_share_tenant), + }); + }); +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/inactive-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/inactive-transaction.manager.ts new file mode 100644 index 0000000..a7a54b1 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/inactive-transaction.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 { TransactionEntity } from '../../entities/transaction.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event'; + +@Injectable() +export class InactiveTransactionManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success inactive data ${this.result.invoice_code}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TransactionChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/index-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/index-transaction.manager.ts new file mode 100644 index 0000000..342e5c1 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/index-transaction.manager.ts @@ -0,0 +1,170 @@ +import { Injectable } from '@nestjs/common'; +import { BaseIndexManager } from 'src/core/modules/domain/usecase/managers/base-index.manager'; +import { TransactionEntity } from '../../entities/transaction.entity'; +import { SelectQueryBuilder } from 'typeorm'; +import { + Param, + RelationParam, +} from 'src/core/modules/domain/entities/base-filter.entity'; +import { BetweenQueryHelper } from 'src/core/helpers/query/between-query.helper'; + +@Injectable() +export class IndexTransactionManager extends BaseIndexManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + this.result?.data?.map((item) => { + Object.assign(item, { + refund_code: item['refund']?.code ?? null, + refund_date: item['refund']?.refund_date ?? null, + }); + + delete item['refund']; + }); + return; + } + + get relations(): RelationParam { + return { + // relation only join (for query purpose) + joinRelations: [], + + // relation join and select (relasi yang ingin ditampilkan), + selectRelations: ['items', 'refund'], + + // relation yang hanya ingin dihitung (akan return number) + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.status`, + `${this.tableName}.invoice_code`, + `${this.tableName}.creator_counter_no`, + `${this.tableName}.booking_date`, + `${this.tableName}.no_of_group`, + `${this.tableName}.type`, + `${this.tableName}.payment_total`, + `${this.tableName}.customer_type`, + `${this.tableName}.customer_name`, + `${this.tableName}.customer_phone`, + `${this.tableName}.customer_description`, + `${this.tableName}.customer_email`, + `${this.tableName}.invoice_date`, + `${this.tableName}.settlement_date`, + `${this.tableName}.created_at`, + `${this.tableName}.creator_id`, + `${this.tableName}.creator_name`, + `${this.tableName}.updated_at`, + `${this.tableName}.editor_id`, + `${this.tableName}.editor_name`, + + `${this.tableName}.payment_type`, + `${this.tableName}.payment_date`, + `${this.tableName}.payment_total_pay`, + `${this.tableName}.payment_type_method_id`, + `${this.tableName}.payment_type_method_name`, + `${this.tableName}.payment_type_method_number`, + + `refund.id`, + `refund.code`, + `refund.refund_date`, + `refund.request_date`, + ]; + } + + get specificFilter(): Param[] { + return [ + { + cols: `${this.tableName}.invoice_code`, + data: this.filterParam.invoice_codes, + }, + { + cols: `${this.tableName}.type::text`, + data: this.filterParam.types, + }, + { + cols: `${this.tableName}.customer_type::text`, + data: this.filterParam.customer_types, + }, + { + cols: `${this.tableName}.customer_name`, + data: this.filterParam.customer_names, + }, + { + cols: `${this.tableName}.payment_type::text`, + data: this.filterParam.payment_types, + }, + { + cols: `${this.tableName}.payment_bank`, + data: this.filterParam.payment_banks, + }, + { + cols: `refund.code`, + data: this.filterParam.refund_codes, + }, + { + cols: `${this.tableName}.creator_name`, + data: this.filterParam.creator_names, + }, + ]; + } + + setQueryFilter( + queryBuilder: SelectQueryBuilder, + ): SelectQueryBuilder { + if (this.filterParam.booking_date_from) { + new BetweenQueryHelper( + queryBuilder, + this.tableName, + 'booking_date', + this.filterParam.booking_date_from, + this.filterParam.booking_date_to, + 'booking_date', + ).getQuery(); + } + + if (this.filterParam.invoice_date_from) { + new BetweenQueryHelper( + queryBuilder, + this.tableName, + 'invoice_date', + this.filterParam.invoice_date_from, + this.filterParam.invoice_date_to, + 'invoice_date', + ).getQuery(); + } + + if (this.filterParam.settlement_date_from) { + new BetweenQueryHelper( + queryBuilder, + this.tableName, + 'settlement_date', + this.filterParam.settlement_date_from, + this.filterParam.settlement_date_to, + 'settlement_date', + ).getQuery(); + } + + if (this.filterParam.request_refund_date_from) { + new BetweenQueryHelper( + queryBuilder, + this.tableName, + 'request_refund_date', + this.filterParam.request_refund_date_from, + this.filterParam.request_refund_date_to, + 'request_refund', + ).getQuery(); + } + queryBuilder.andWhere(`${this.tableName}.is_recap_transaction is false`); + return queryBuilder; + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/managers/update-transaction.manager.ts b/src/modules/transaction/transaction/domain/usecases/managers/update-transaction.manager.ts new file mode 100644 index 0000000..d4d08d5 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/managers/update-transaction.manager.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@nestjs/common'; +import { BaseUpdateManager } from 'src/core/modules/domain/usecase/managers/base-update.manager'; +import { TransactionEntity } from '../../entities/transaction.entity'; +import { TransactionModel } from '../../../data/models/transaction.model'; +import { TransactionUpdatedEvent } from '../../entities/event/transaction-updated.event'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TransactionType } from '../../../constants'; +import { mappingRevertTransaction } from './helpers/mapping-transaction.helper'; + +@Injectable() +export class UpdateTransactionManager extends BaseUpdateManager { + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + mappingRevertTransaction(this.data, TransactionType.ADMIN); + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get entityTarget(): any { + return TransactionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TransactionUpdatedEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/transaction-data.orchestrator.ts b/src/modules/transaction/transaction/domain/usecases/transaction-data.orchestrator.ts new file mode 100644 index 0000000..3aa32a8 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/transaction-data.orchestrator.ts @@ -0,0 +1,125 @@ +import { Injectable } from '@nestjs/common'; +import { CreateTransactionManager } from './managers/create-transaction.manager'; +import { TransactionDataService } from '../../data/services/transaction-data.service'; +import { TransactionEntity } from '../entities/transaction.entity'; +import { DeleteTransactionManager } from './managers/delete-transaction.manager'; +import { UpdateTransactionManager } from './managers/update-transaction.manager'; +import { ConfirmTransactionManager } from './managers/confirm-transaction.manager'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchConfirmTransactionManager } from './managers/batch-confirm-transaction.manager'; +import { BatchDeleteTransactionManager } from './managers/batch-delete-transaction.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { CancelTransactionManager } from './managers/cancel-transaction.manager'; +import { BatchCancelTransactionManager } from './managers/batch-cancel-transaction.manager'; +import { ConfirmDataTransactionManager } from './managers/confirm-data-transaction.manager'; +import { BatchConfirmDataTransactionManager } from './managers/batch-confirm-data-transaction.manager'; +import { MidtransService } from 'src/modules/configuration/midtrans/data/services/midtrans.service'; + +@Injectable() +export class TransactionDataOrchestrator { + constructor( + private createManager: CreateTransactionManager, + private updateManager: UpdateTransactionManager, + private confirmManager: ConfirmTransactionManager, + private batchConfirmManager: BatchConfirmTransactionManager, + private confirmDataManager: ConfirmDataTransactionManager, + private batchConfirmDataManager: BatchConfirmDataTransactionManager, + private deleteManager: DeleteTransactionManager, + private batchDeleteManager: BatchDeleteTransactionManager, + private cancelManager: CancelTransactionManager, + private batchCancelManager: BatchCancelTransactionManager, + private serviceData: TransactionDataService, + private midtransService: MidtransService, + ) {} + + async create(data): Promise { + this.createManager.setData(data); + this.createManager.setService(this.serviceData, TABLE_NAME.TRANSACTION); + await this.createManager.execute(); + return this.createManager.getResult(); + } + + async update(dataId, data): Promise { + this.updateManager.setData(dataId, data); + this.updateManager.setService(this.serviceData, TABLE_NAME.TRANSACTION); + await this.updateManager.execute(); + return this.updateManager.getResult(); + } + + async delete(dataId): Promise { + this.deleteManager.setData(dataId); + this.deleteManager.setService(this.serviceData, TABLE_NAME.TRANSACTION); + await this.deleteManager.execute(); + return this.deleteManager.getResult(); + } + + async batchDelete(dataIds: string[]): Promise { + this.batchDeleteManager.setData(dataIds); + this.batchDeleteManager.setService( + this.serviceData, + TABLE_NAME.TRANSACTION, + ); + await this.batchDeleteManager.execute(); + return this.batchDeleteManager.getResult(); + } + + async cancel(dataId): Promise { + this.cancelManager.setData(dataId, STATUS.CANCEL); + this.cancelManager.setService(this.serviceData, TABLE_NAME.TRANSACTION); + await this.cancelManager.execute(); + return this.cancelManager.getResult(); + } + + async batchCancel(dataIds: string[]): Promise { + this.batchCancelManager.setData(dataIds, STATUS.CANCEL); + this.batchCancelManager.setService( + this.serviceData, + TABLE_NAME.TRANSACTION, + ); + await this.batchCancelManager.execute(); + return this.batchCancelManager.getResult(); + } + + async confirm(dataId): Promise { + this.confirmManager.setData(dataId, STATUS.ACTIVE); + this.confirmManager.setService( + this.serviceData, + TABLE_NAME.TRANSACTION, + this.midtransService, + ); + 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.TRANSACTION, + this.midtransService, + ); + await this.batchConfirmManager.execute(); + return this.batchConfirmManager.getResult(); + } + + async confirmData(dataId): Promise { + this.confirmDataManager.setData(dataId, STATUS.ACTIVE); + this.confirmDataManager.setService( + this.serviceData, + TABLE_NAME.TRANSACTION, + ); + await this.confirmDataManager.execute(); + return this.confirmDataManager.getResult(); + } + + async batchConfirmData(dataIds: string[]): Promise { + this.batchConfirmDataManager.setData(dataIds, STATUS.ACTIVE); + this.batchConfirmDataManager.setService( + this.serviceData, + TABLE_NAME.TRANSACTION, + ); + await this.batchConfirmDataManager.execute(); + return this.batchConfirmDataManager.getResult(); + } +} diff --git a/src/modules/transaction/transaction/domain/usecases/transaction-read.orchestrator.ts b/src/modules/transaction/transaction/domain/usecases/transaction-read.orchestrator.ts new file mode 100644 index 0000000..4f39239 --- /dev/null +++ b/src/modules/transaction/transaction/domain/usecases/transaction-read.orchestrator.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@nestjs/common'; +import { IndexTransactionManager } from './managers/index-transaction.manager'; +import { TransactionReadService } from '../../data/services/transaction-read.service'; +import { TransactionEntity } from '../entities/transaction.entity'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { BaseReadOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-read.orchestrator'; +import { DetailTransactionManager } from './managers/detail-transaction.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@Injectable() +export class TransactionReadOrchestrator extends BaseReadOrchestrator { + constructor( + private indexManager: IndexTransactionManager, + private detailManager: DetailTransactionManager, + private serviceData: TransactionReadService, + ) { + super(); + } + + async index(params): Promise> { + this.indexManager.setFilterParam(params); + this.indexManager.setService(this.serviceData, TABLE_NAME.TRANSACTION); + await this.indexManager.execute(); + return this.indexManager.getResult(); + } + + async detail(dataId: string): Promise { + this.detailManager.setData(dataId); + this.detailManager.setService(this.serviceData, TABLE_NAME.TRANSACTION); + await this.detailManager.execute(); + return this.detailManager.getResult(); + } +} diff --git a/src/modules/transaction/transaction/index.ts b/src/modules/transaction/transaction/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/transaction/transaction/infrastructure/dto/filter-transaction.dto.ts b/src/modules/transaction/transaction/infrastructure/dto/filter-transaction.dto.ts new file mode 100644 index 0000000..fc83ebf --- /dev/null +++ b/src/modules/transaction/transaction/infrastructure/dto/filter-transaction.dto.ts @@ -0,0 +1,75 @@ +import { BaseFilterDto } from 'src/core/modules/infrastructure/dto/base-filter.dto'; +import { FilterTransactionEntity } from '../../domain/entities/filter-transaction.entity'; +import { Transform } from 'class-transformer'; +import { ApiProperty } from '@nestjs/swagger'; + +export class FilterTransactionDto + extends BaseFilterDto + implements FilterTransactionEntity +{ + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + types?: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + customer_types?: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + customer_names?: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + payment_types?: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + payment_banks?: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + invoice_codes?: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + refund_codes?: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + creator_names?: string[]; + + @ApiProperty({ type: Date, required: false }) + booking_date_from?: Date; + + @ApiProperty({ type: Date, required: false }) + booking_date_to?: Date; + + @ApiProperty({ type: Date, required: false }) + invoice_date_from?: Date; + + @ApiProperty({ type: Date, required: false }) + invoice_date_to?: Date; + + @ApiProperty({ type: Date, required: false }) + settlement_date_from?: Date; + + @ApiProperty({ type: Date, required: false }) + settlement_date_to?: Date; +} diff --git a/src/modules/transaction/transaction/infrastructure/dto/transaction-item.dto.ts b/src/modules/transaction/transaction/infrastructure/dto/transaction-item.dto.ts new file mode 100644 index 0000000..4ccb047 --- /dev/null +++ b/src/modules/transaction/transaction/infrastructure/dto/transaction-item.dto.ts @@ -0,0 +1,39 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { IsNumber, IsObject } from 'class-validator'; +import { ItemEntity } from 'src/modules/item-related/item/domain/entities/item.entity'; + +export class TransactionItemDto { + @ApiProperty({ + type: Object, + required: true, + example: { + id: 'uuid', + name: 'Bundling 1', + price: 10000, + hpp: 1000, + tenant: { + id: 'uuid', + name: 'tenant 1', + share_margin: 10, + }, + }, + }) + @IsObject() + item: ItemEntity; + + @ApiProperty({ + type: Number, + required: true, + example: 1, + }) + @IsNumber() + total_price: number; + + @ApiProperty({ + type: Number, + required: true, + example: 1, + }) + @IsNumber() + qty: number; +} diff --git a/src/modules/transaction/transaction/infrastructure/dto/transaction.dto.ts b/src/modules/transaction/transaction/infrastructure/dto/transaction.dto.ts new file mode 100644 index 0000000..cbb954b --- /dev/null +++ b/src/modules/transaction/transaction/infrastructure/dto/transaction.dto.ts @@ -0,0 +1,171 @@ +import { BaseStatusDto } from 'src/core/modules/infrastructure/dto/base-status.dto'; +import { TransactionUserType, TransactionPaymentType } from '../../constants'; +import { ApiProperty } from '@nestjs/swagger'; +import { + IsArray, + IsEmail, + IsNumber, + IsObject, + IsString, + ValidateIf, +} from 'class-validator'; +import { SeasonPeriodEntity } from 'src/modules/season-related/season-period/domain/entities/season-period.entity'; +import { TransactionItemEntity } from '../../domain/entities/transaction-item.entity'; + +export class TransactionDto extends BaseStatusDto { + @ApiProperty({ + type: Object, + required: false, + example: { + id: 'uuid', + season_type: { + id: 'uuid', + name: 'high season', + }, + }, + }) + @IsObject() + @ValidateIf((body) => body.season_period) + season_period: SeasonPeriodEntity; + + @ApiProperty({ + type: String, + required: true, + example: TransactionUserType.GROUP, + }) + @IsString() + customer_type: TransactionUserType; + + @ApiProperty({ + type: String, + required: true, + example: 'Andika', + }) + @IsString() + customer_name: string; + + @ApiProperty({ + type: String, + required: false, + example: '0823...', + }) + @ValidateIf((body) => body.customer_phone) + customer_phone: string; + + @ApiProperty({ required: false, example: 'andika@mail.com' }) + @IsEmail({ ignore_max_length: true }) + @ValidateIf((body) => body.customer_email) + customer_email: string; + + @ApiProperty({ + type: String, + required: false, + example: 'Influencer', + }) + @ValidateIf((body) => body.customer_description) + customer_description: string; + + @ApiProperty({ + type: Number, + required: true, + example: 1, + }) + @IsNumber() + no_of_group: number; + + @ApiProperty({ + type: Date, + required: true, + example: '2024-01-01', + }) + booking_date: Date; + + @ApiProperty({ + type: String, + required: false, + }) + @ValidateIf((body) => body.discount_code) + discount_code: string; + + @ApiProperty({ + type: Number, + required: false, + }) + @ValidateIf((body) => body.discount_percentage) + discount_percentage: number; + + @ApiProperty({ + type: Number, + required: false, + }) + @ValidateIf((body) => body.discount_value) + discount_value: number; + + @ApiProperty({ + type: String, + required: true, + example: TransactionPaymentType.MIDTRANS, + }) + @IsString() + payment_type: TransactionPaymentType; + + @ApiProperty({ + type: Number, + required: true, + example: 7000000, + }) + @IsNumber() + payment_sub_total: number; + + @ApiProperty({ + type: Number, + required: false, + example: 3500000, + }) + @IsNumber() + @ValidateIf((body) => body.payment_discount_total) + payment_discount_total: number; + + @ApiProperty({ + type: Number, + required: true, + example: 3500000, + }) + @IsNumber() + payment_total: number; + + @ApiProperty({ + type: [Object], + required: true, + example: [ + { + item: { + id: '68aa12f7-2cce-422b-9bae-185eb1343b94', + created_at: '1718876384378', + status: 'active', + name: 'tes', + item_type: 'bundling', + hpp: '100000', + base_price: '100000', + limit_type: 'no limit', + limit_value: 0, + item_category: { + id: 'ab15981a-a656-4efc-856c-b2abfbe30979', + name: 'Kategori Bundling 2', + }, + bundling_items: [ + { + id: 'bd5a7a38-df25-4203-a1cd-bf94867946b2', + name: 'Wahana 21 panjangggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg', + }, + ], + tenant: null, + }, + qty: 40, + total_price: 4000000, + }, + ], + }) + @IsArray() + items: TransactionItemEntity[]; +} diff --git a/src/modules/transaction/transaction/infrastructure/transaction-data.controller.ts b/src/modules/transaction/transaction/infrastructure/transaction-data.controller.ts new file mode 100644 index 0000000..8866cde --- /dev/null +++ b/src/modules/transaction/transaction/infrastructure/transaction-data.controller.ts @@ -0,0 +1,78 @@ +import { + Body, + Controller, + Delete, + Param, + Patch, + Post, + Put, +} from '@nestjs/common'; +import { TransactionDataOrchestrator } from '../domain/usecases/transaction-data.orchestrator'; +import { TransactionDto } from './dto/transaction.dto'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { TransactionEntity } from '../domain/entities/transaction.entity'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto'; +import { Public } from 'src/core/guards'; + +@ApiTags(`${MODULE_NAME.TRANSACTION.split('-').join(' ')} - data`) +@Controller(`v1/${MODULE_NAME.TRANSACTION}`) +@Public(false) +@ApiBearerAuth('JWT') +export class TransactionDataController { + constructor(private orchestrator: TransactionDataOrchestrator) {} + + @Post() + async create(@Body() data: TransactionDto): 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/confirm-data') + async confirmData(@Param('id') dataId: string): Promise { + return await this.orchestrator.confirmData(dataId); + } + + @Put('/batch-confirm-data') + async batchConfirmData(@Body() body: BatchIdsDto): Promise { + return await this.orchestrator.batchConfirmData(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/cancel') + async cancel(@Param('id') dataId: string): Promise { + return await this.orchestrator.cancel(dataId); + } + + @Put('/batch-cancel') + async batchCancel(@Body() body: BatchIdsDto): Promise { + return await this.orchestrator.batchCancel(body.ids); + } + + @Put(':id') + async update( + @Param('id') dataId: string, + @Body() data: TransactionDto, + ): Promise { + return await this.orchestrator.update(dataId, data); + } + + @Delete(':id') + async delete(@Param('id') dataId: string): Promise { + return await this.orchestrator.delete(dataId); + } +} diff --git a/src/modules/transaction/transaction/infrastructure/transaction-read.controller.ts b/src/modules/transaction/transaction/infrastructure/transaction-read.controller.ts new file mode 100644 index 0000000..099cc0d --- /dev/null +++ b/src/modules/transaction/transaction/infrastructure/transaction-read.controller.ts @@ -0,0 +1,30 @@ +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { FilterTransactionDto } from './dto/filter-transaction.dto'; +import { Pagination } from 'src/core/response'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { TransactionEntity } from '../domain/entities/transaction.entity'; +import { TransactionReadOrchestrator } from '../domain/usecases/transaction-read.orchestrator'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { Public } from 'src/core/guards'; + +@ApiTags(`${MODULE_NAME.TRANSACTION.split('-').join(' ')} - read`) +@Controller(`v1/${MODULE_NAME.TRANSACTION}`) +@Public(false) +@ApiBearerAuth('JWT') +export class TransactionReadController { + constructor(private orchestrator: TransactionReadOrchestrator) {} + + @Get() + @Pagination() + async index( + @Query() params: FilterTransactionDto, + ): Promise> { + return await this.orchestrator.index(params); + } + + @Get(':id') + async detail(@Param('id') id: string): Promise { + return await this.orchestrator.detail(id); + } +} diff --git a/src/modules/transaction/transaction/transaction.module.ts b/src/modules/transaction/transaction/transaction.module.ts new file mode 100644 index 0000000..a626078 --- /dev/null +++ b/src/modules/transaction/transaction/transaction.module.ts @@ -0,0 +1,80 @@ +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 { TransactionDataService } from './data/services/transaction-data.service'; +import { TransactionReadService } from './data/services/transaction-read.service'; +import { TransactionReadController } from './infrastructure/transaction-read.controller'; +import { TransactionReadOrchestrator } from './domain/usecases/transaction-read.orchestrator'; +import { TransactionDataController } from './infrastructure/transaction-data.controller'; +import { TransactionDataOrchestrator } from './domain/usecases/transaction-data.orchestrator'; +import { CreateTransactionManager } from './domain/usecases/managers/create-transaction.manager'; +import { CqrsModule } from '@nestjs/cqrs'; +import { IndexTransactionManager } from './domain/usecases/managers/index-transaction.manager'; +import { DeleteTransactionManager } from './domain/usecases/managers/delete-transaction.manager'; +import { UpdateTransactionManager } from './domain/usecases/managers/update-transaction.manager'; +import { ConfirmTransactionManager } from './domain/usecases/managers/confirm-transaction.manager'; +import { DetailTransactionManager } from './domain/usecases/managers/detail-transaction.manager'; +import { BatchDeleteTransactionManager } from './domain/usecases/managers/batch-delete-transaction.manager'; +import { BatchConfirmTransactionManager } from './domain/usecases/managers/batch-confirm-transaction.manager'; +import { TransactionModel } from './data/models/transaction.model'; +import { TransactionItemModel } from './data/models/transaction-item.model'; +import { TransactionTaxModel } from './data/models/transaction-tax.model'; +import { CancelTransactionManager } from './domain/usecases/managers/cancel-transaction.manager'; +import { BatchCancelTransactionManager } from './domain/usecases/managers/batch-cancel-transaction.manager'; +import { ConfirmDataTransactionManager } from './domain/usecases/managers/confirm-data-transaction.manager'; +import { BatchConfirmDataTransactionManager } from './domain/usecases/managers/batch-confirm-data-transaction.manager'; +import { PosTransactionHandler } from './domain/usecases/handlers/pos-transaction.handler'; +import { TaxDataService } from '../tax/data/services/tax-data.service'; +import { SalesPriceFormulaDataService } from '../sales-price-formula/data/services/sales-price-formula-data.service'; +import { SalesPriceFormulaModel } from '../sales-price-formula/data/models/sales-price-formula.model'; +import { TaxModel } from '../tax/data/models/tax.model'; +import { SettledTransactionHandler } from './domain/usecases/handlers/settled-transaction.handler'; +import { RefundUpdatedHandler } from './domain/usecases/handlers/refund-update.handler'; +import { MidtransCallbackHandler } from './domain/usecases/handlers/midtrans-transaction-callback.handler'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature( + [ + TransactionModel, + TransactionItemModel, + TransactionTaxModel, + TaxModel, + SalesPriceFormulaModel, + ], + CONNECTION_NAME.DEFAULT, + ), + CqrsModule, + ], + controllers: [TransactionDataController, TransactionReadController], + providers: [ + RefundUpdatedHandler, + PosTransactionHandler, + MidtransCallbackHandler, + SettledTransactionHandler, + + IndexTransactionManager, + DetailTransactionManager, + CreateTransactionManager, + DeleteTransactionManager, + UpdateTransactionManager, + ConfirmTransactionManager, + BatchDeleteTransactionManager, + BatchConfirmTransactionManager, + CancelTransactionManager, + BatchCancelTransactionManager, + ConfirmDataTransactionManager, + BatchConfirmDataTransactionManager, + + TransactionDataService, + TransactionReadService, + TaxDataService, + SalesPriceFormulaDataService, + + TransactionDataOrchestrator, + TransactionReadOrchestrator, + ], +}) +export class TransactionModule {} diff --git a/src/modules/transaction/vip-category/domain/entities/filter-vip-category.entity.ts b/src/modules/transaction/vip-category/domain/entities/filter-vip-category.entity.ts index bb53a73..e23eaaa 100644 --- a/src/modules/transaction/vip-category/domain/entities/filter-vip-category.entity.ts +++ b/src/modules/transaction/vip-category/domain/entities/filter-vip-category.entity.ts @@ -1,3 +1,3 @@ import { BaseFilterEntity } from 'src/core/modules/domain/entities/base-filter.entity'; -export interface FilterVipCategoryEntity extends BaseFilterEntity {} +export type FilterVipCategoryEntity = BaseFilterEntity; diff --git a/src/modules/transaction/vip-category/domain/usecases/managers/create-vip-category.manager.ts b/src/modules/transaction/vip-category/domain/usecases/managers/create-vip-category.manager.ts index d2f77ac..83a2421 100644 --- a/src/modules/transaction/vip-category/domain/usecases/managers/create-vip-category.manager.ts +++ b/src/modules/transaction/vip-category/domain/usecases/managers/create-vip-category.manager.ts @@ -19,7 +19,9 @@ export class CreateVipCategoryManager extends BaseCreateManager {} + async generateConfig(): Promise { + // TODO: Implement logic here + } get validateRelations(): validateRelations[] { return []; diff --git a/src/modules/transaction/vip-category/domain/usecases/vip-category-data.orchestrator.ts b/src/modules/transaction/vip-category/domain/usecases/vip-category-data.orchestrator.ts index 92b6c8a..f5102a5 100644 --- a/src/modules/transaction/vip-category/domain/usecases/vip-category-data.orchestrator.ts +++ b/src/modules/transaction/vip-category/domain/usecases/vip-category-data.orchestrator.ts @@ -49,7 +49,7 @@ export class VipCategoryDataOrchestrator extends BaseDataTransactionOrchestrator return this.updateManager.getResult(); } - async delete(dataId): Promise { + async delete(dataId): Promise { this.deleteManager.setData(dataId); this.deleteManager.setService(this.serviceData, TABLE_NAME.VIP_CATEGORY); await this.deleteManager.execute(); @@ -66,7 +66,7 @@ export class VipCategoryDataOrchestrator extends BaseDataTransactionOrchestrator return this.batchDeleteManager.getResult(); } - async active(dataId): Promise { + async active(dataId): Promise { this.activeManager.setData(dataId, STATUS.ACTIVE); this.activeManager.setService(this.serviceData, TABLE_NAME.VIP_CATEGORY); await this.activeManager.execute(); @@ -83,7 +83,7 @@ export class VipCategoryDataOrchestrator extends BaseDataTransactionOrchestrator return this.batchActiveManager.getResult(); } - async confirm(dataId): Promise { + async confirm(dataId): Promise { this.confirmManager.setData(dataId, STATUS.ACTIVE); this.confirmManager.setService(this.serviceData, TABLE_NAME.VIP_CATEGORY); await this.confirmManager.execute(); @@ -100,7 +100,7 @@ export class VipCategoryDataOrchestrator extends BaseDataTransactionOrchestrator return this.batchConfirmManager.getResult(); } - async inactive(dataId): Promise { + async inactive(dataId): Promise { this.inactiveManager.setData(dataId, STATUS.INACTIVE); this.inactiveManager.setService(this.serviceData, TABLE_NAME.VIP_CATEGORY); await this.inactiveManager.execute(); diff --git a/src/modules/transaction/vip-category/infrastructure/vip-category-data.controller.ts b/src/modules/transaction/vip-category/infrastructure/vip-category-data.controller.ts index 173f9b4..64feb30 100644 --- a/src/modules/transaction/vip-category/infrastructure/vip-category-data.controller.ts +++ b/src/modules/transaction/vip-category/infrastructure/vip-category-data.controller.ts @@ -34,7 +34,7 @@ export class VipCategoryDataController { } @Patch(':id/active') - async active(@Param('id') dataId: string): Promise { + async active(@Param('id') dataId: string): Promise { return await this.orchestrator.active(dataId); } @@ -44,7 +44,7 @@ export class VipCategoryDataController { } @Patch(':id/confirm') - async confirm(@Param('id') dataId: string): Promise { + async confirm(@Param('id') dataId: string): Promise { return await this.orchestrator.confirm(dataId); } @@ -54,7 +54,7 @@ export class VipCategoryDataController { } @Patch(':id/inactive') - async inactive(@Param('id') dataId: string): Promise { + async inactive(@Param('id') dataId: string): Promise { return await this.orchestrator.inactive(dataId); } @@ -72,7 +72,7 @@ export class VipCategoryDataController { } @Delete(':id') - async delete(@Param('id') dataId: string): Promise { + async delete(@Param('id') dataId: string): Promise { return await this.orchestrator.delete(dataId); } } diff --git a/src/modules/transaction/vip-code/domain/entities/filter-vip-code.entity.ts b/src/modules/transaction/vip-code/domain/entities/filter-vip-code.entity.ts index e971665..bb7ec07 100644 --- a/src/modules/transaction/vip-code/domain/entities/filter-vip-code.entity.ts +++ b/src/modules/transaction/vip-code/domain/entities/filter-vip-code.entity.ts @@ -1,3 +1,3 @@ import { BaseFilterEntity } from 'src/core/modules/domain/entities/base-filter.entity'; -export interface FilterVipCodeEntity extends BaseFilterEntity {} +export type FilterVipCodeEntity = BaseFilterEntity; diff --git a/src/modules/transaction/vip-code/domain/usecases/handlers/create-vip-code.handler.ts b/src/modules/transaction/vip-code/domain/usecases/handlers/create-vip-code.handler.ts new file mode 100644 index 0000000..2c07dcb --- /dev/null +++ b/src/modules/transaction/vip-code/domain/usecases/handlers/create-vip-code.handler.ts @@ -0,0 +1,40 @@ +import { EventsHandler, IEventHandler } from '@nestjs/cqrs'; +import { ChangeDocEvent } from 'src/modules/configuration/couch/domain/events/change-doc.event'; +import { VipCodeDataService } from '../../../data/services/vip-code-data.service'; +import { VipCodeModel } from '../../../data/models/vip-code.model'; +import { CouchService } from 'src/modules/configuration/couch/data/services/couch.service'; + +@EventsHandler(ChangeDocEvent) +export class CreateVipCodeHandler implements IEventHandler { + constructor( + private dataService: VipCodeDataService, + private couchService: CouchService, + ) {} + + async handle(event: ChangeDocEvent) { + const database = event.data.database; + const data = event.data.data; + + if (database != 'vip_code') return; + + const queryRunner = this.dataService + .getRepository() + .manager.connection.createQueryRunner(); + + // jika delete + if (data._deleted ?? false) { + } else { + const dataMapped = { + ...data, + id: data._id ?? data.id, + vip_category_id: data.vip_category?._id ?? data.vip_category?.id, + }; + + try { + await this.dataService.create(queryRunner, VipCodeModel, dataMapped); + } catch (error) { + await this.couchService.createDoc(data, 'error_vip_code'); + } + } + } +} diff --git a/src/modules/transaction/vip-code/domain/usecases/managers/create-vip-code.manager.ts b/src/modules/transaction/vip-code/domain/usecases/managers/create-vip-code.manager.ts index 947e65c..e235821 100644 --- a/src/modules/transaction/vip-code/domain/usecases/managers/create-vip-code.manager.ts +++ b/src/modules/transaction/vip-code/domain/usecases/managers/create-vip-code.manager.ts @@ -35,7 +35,7 @@ export class CreateVipCodeManager extends BaseCreateManager { return [ { topic: VipCodeCreatedEvent, - data: this.data, + relations: ['vip_category'], }, ]; } diff --git a/src/modules/transaction/vip-code/domain/usecases/managers/geneate-vip-code.manager.ts b/src/modules/transaction/vip-code/domain/usecases/managers/geneate-vip-code.manager.ts index 071ae18..4d5fe3e 100644 --- a/src/modules/transaction/vip-code/domain/usecases/managers/geneate-vip-code.manager.ts +++ b/src/modules/transaction/vip-code/domain/usecases/managers/geneate-vip-code.manager.ts @@ -2,6 +2,10 @@ import { BaseCustomManager } from 'src/core/modules/domain/usecase/managers/base import { Injectable } from '@nestjs/common'; import { VipCodeEntity } from '../../entities/vip-code.entity'; import { EventTopics } from 'src/core/strings/constants/interface.constants'; +import { + generateCodeDate, + generateRandom, +} from './helpers/generate-random.helper'; @Injectable() export class GenerateVipCodeManager extends BaseCustomManager { @@ -14,19 +18,11 @@ export class GenerateVipCodeManager extends BaseCustomManager { } process(): Promise { - const date = new Date(); + const month_year = generateCodeDate(); + const char = generateRandom(1); + const number = generateRandom(2, true); - // get month dan year (dua digit) - const month = - date.getMonth() < 10 - ? `0${date.getMonth() + 1}` - : (date.getMonth() + 1).toString(); - const year = date.getFullYear().toString().slice(-2); - - const char = this.generateRandom(1); - const number = this.generateRandom(2, true); - - this.result = `${month}${year}${char}${number}`; + this.result = `${month_year}${char}${number}`; return; } @@ -45,18 +41,4 @@ export class GenerateVipCodeManager extends BaseCustomManager { get eventTopics(): EventTopics[] { return []; } - - generateRandom(length: number, is_number?: boolean): string { - let result = ''; - let base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - if (is_number) base = '123456789'; - - let counter = 0; - while (counter < length) { - result += base.charAt(Math.floor(Math.random() * base.length)); - counter += 1; - } - - return result; - } } diff --git a/src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper.ts b/src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper.ts new file mode 100644 index 0000000..4c65f72 --- /dev/null +++ b/src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper.ts @@ -0,0 +1,24 @@ +export function generateRandom(length: number, is_number?: boolean): string { + let result = ''; + let base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + if (is_number) base = '123456789'; + + let counter = 0; + while (counter < length) { + result += base.charAt(Math.floor(Math.random() * base.length)); + counter += 1; + } + + return result; +} + +export function generateCodeDate() { + const date = new Date(); + const month = + date.getMonth() < 10 + ? `0${date.getMonth() + 1}` + : (date.getMonth() + 1).toString(); + const year = date.getFullYear().toString().slice(-2); + + return `${month}${year}`; +} diff --git a/src/modules/transaction/vip-code/domain/usecases/vip-code-data.orchestrator.ts b/src/modules/transaction/vip-code/domain/usecases/vip-code-data.orchestrator.ts index c04977e..85d9242 100644 --- a/src/modules/transaction/vip-code/domain/usecases/vip-code-data.orchestrator.ts +++ b/src/modules/transaction/vip-code/domain/usecases/vip-code-data.orchestrator.ts @@ -21,7 +21,7 @@ export class VipCodeDataOrchestrator extends BaseDataOrchestrator update(dataId: string, data: VipCodeEntity): Promise { throw new Error('Method not implemented.'); } - delete(dataId: string): Promise { + delete(dataId: string): Promise { throw new Error('Method not implemented.'); } batchDelete(dataIds: string[]): Promise { diff --git a/src/modules/transaction/vip-code/infrastructure/vip-code-data.controller.ts b/src/modules/transaction/vip-code/infrastructure/vip-code-data.controller.ts index 3ae91ee..a3f89b5 100644 --- a/src/modules/transaction/vip-code/infrastructure/vip-code-data.controller.ts +++ b/src/modules/transaction/vip-code/infrastructure/vip-code-data.controller.ts @@ -19,7 +19,7 @@ export class VipCodeDataController { } @Post('generate-code') - async generateCOde(): Promise { + async generateCOde(): Promise { return await this.orchestrator.generateCode(); } @@ -29,7 +29,7 @@ export class VipCodeDataController { // } // @Delete(':id') - // async delete(@Param('id') dataId: string): Promise { + // async delete(@Param('id') dataId: string): Promise { // return await this.orchestrator.delete(dataId); // } } diff --git a/src/modules/transaction/vip-code/vip-code.module.ts b/src/modules/transaction/vip-code/vip-code.module.ts index 68f7dd1..e3b98ca 100644 --- a/src/modules/transaction/vip-code/vip-code.module.ts +++ b/src/modules/transaction/vip-code/vip-code.module.ts @@ -13,6 +13,8 @@ import { CqrsModule } from '@nestjs/cqrs'; import { IndexVipCodeManager } from './domain/usecases/managers/index-vip-code.manager'; import { VipCodeModel } from './data/models/vip-code.model'; import { GenerateVipCodeManager } from './domain/usecases/managers/geneate-vip-code.manager'; +import { CreateVipCodeHandler } from './domain/usecases/handlers/create-vip-code.handler'; +import { CouchService } from 'src/modules/configuration/couch/data/services/couch.service'; @Module({ imports: [ @@ -22,10 +24,13 @@ import { GenerateVipCodeManager } from './domain/usecases/managers/geneate-vip-c ], controllers: [VipCodeDataController, VipCodeReadController], providers: [ + CreateVipCodeHandler, + IndexVipCodeManager, CreateVipCodeManager, GenerateVipCodeManager, + CouchService, VipCodeDataService, VipCodeReadService, diff --git a/src/modules/user-related/tenant/domain/usecases/managers/create-tenant.manager.ts b/src/modules/user-related/tenant/domain/usecases/managers/create-tenant.manager.ts index 65b4736..3067935 100644 --- a/src/modules/user-related/tenant/domain/usecases/managers/create-tenant.manager.ts +++ b/src/modules/user-related/tenant/domain/usecases/managers/create-tenant.manager.ts @@ -30,7 +30,9 @@ export class CreateTenantManager extends BaseCreateManager { return; } - async generateConfig(): Promise {} + async generateConfig(): Promise { + // TODO: Implement logic here + } get uniqueColumns(): columnUniques[] { return [ diff --git a/src/modules/user-related/tenant/domain/usecases/tenant-data.orchestrator.ts b/src/modules/user-related/tenant/domain/usecases/tenant-data.orchestrator.ts index 23942e6..7b11ae7 100644 --- a/src/modules/user-related/tenant/domain/usecases/tenant-data.orchestrator.ts +++ b/src/modules/user-related/tenant/domain/usecases/tenant-data.orchestrator.ts @@ -58,7 +58,7 @@ export class TenantDataOrchestrator extends BaseDataTransactionOrchestrator { + async delete(dataId): Promise { this.deleteManager.setData(dataId); this.deleteManager.setService(this.serviceData, TABLE_NAME.TENANT); await this.deleteManager.execute(); @@ -72,7 +72,7 @@ export class TenantDataOrchestrator extends BaseDataTransactionOrchestrator { + async active(dataId): Promise { this.activeManager.setData(dataId, STATUS.ACTIVE); this.activeManager.setService(this.serviceData, TABLE_NAME.TENANT); await this.activeManager.execute(); @@ -86,7 +86,7 @@ export class TenantDataOrchestrator extends BaseDataTransactionOrchestrator { + async confirm(dataId): Promise { this.confirmManager.setData(dataId, STATUS.ACTIVE); this.confirmManager.setService(this.serviceData, TABLE_NAME.TENANT); await this.confirmManager.execute(); @@ -100,7 +100,7 @@ export class TenantDataOrchestrator extends BaseDataTransactionOrchestrator { + async inactive(dataId): Promise { this.inactiveManager.setData(dataId, STATUS.INACTIVE); this.inactiveManager.setService(this.serviceData, TABLE_NAME.TENANT); await this.inactiveManager.execute(); diff --git a/src/modules/user-related/tenant/infrastructure/tenant-data.controller.ts b/src/modules/user-related/tenant/infrastructure/tenant-data.controller.ts index b9fc12a..afe7961 100644 --- a/src/modules/user-related/tenant/infrastructure/tenant-data.controller.ts +++ b/src/modules/user-related/tenant/infrastructure/tenant-data.controller.ts @@ -36,7 +36,7 @@ export class TenantDataController { } @Patch(':id/active') - async active(@Param('id') dataId: string): Promise { + async active(@Param('id') dataId: string): Promise { return await this.orchestrator.active(dataId); } @@ -46,7 +46,7 @@ export class TenantDataController { } @Patch(':id/confirm') - async confirm(@Param('id') dataId: string): Promise { + async confirm(@Param('id') dataId: string): Promise { return await this.orchestrator.confirm(dataId); } @@ -56,7 +56,7 @@ export class TenantDataController { } @Patch(':id/inactive') - async inactive(@Param('id') dataId: string): Promise { + async inactive(@Param('id') dataId: string): Promise { return await this.orchestrator.inactive(dataId); } @@ -82,7 +82,7 @@ export class TenantDataController { } @Delete(':id') - async delete(@Param('id') dataId: string): Promise { + async delete(@Param('id') dataId: string): Promise { return await this.orchestrator.delete(dataId); } } diff --git a/src/modules/user-related/tenant/infrastructure/tenant-item-data.controller.ts b/src/modules/user-related/tenant/infrastructure/tenant-item-data.controller.ts index 24bb7a6..778dc75 100644 --- a/src/modules/user-related/tenant/infrastructure/tenant-item-data.controller.ts +++ b/src/modules/user-related/tenant/infrastructure/tenant-item-data.controller.ts @@ -43,7 +43,7 @@ export class TenantItemDataController { async active( @Param('tenant_id') tenant_id: string, @Param('id') dataId: string, - ): Promise { + ): Promise { return await this.orchestrator.active(dataId); } @@ -59,7 +59,7 @@ export class TenantItemDataController { async confirm( @Param('tenant_id') tenant_id: string, @Param('id') dataId: string, - ): Promise { + ): Promise { return await this.orchestrator.confirm(dataId); } @@ -75,7 +75,7 @@ export class TenantItemDataController { async inactive( @Param('tenant_id') tenant_id: string, @Param('id') dataId: string, - ): Promise { + ): Promise { return await this.orchestrator.inactive(dataId); } @@ -100,7 +100,7 @@ export class TenantItemDataController { async delete( @Param('tenant_id') tenant_id: string, @Param('id') dataId: string, - ): Promise { + ): Promise { return await this.orchestrator.delete(dataId); } } diff --git a/src/modules/user-related/user-privilege/domain/entities/filter-user-privilege.entity.ts b/src/modules/user-related/user-privilege/domain/entities/filter-user-privilege.entity.ts index cb66f26..eb95688 100644 --- a/src/modules/user-related/user-privilege/domain/entities/filter-user-privilege.entity.ts +++ b/src/modules/user-related/user-privilege/domain/entities/filter-user-privilege.entity.ts @@ -1,3 +1,3 @@ import { BaseFilterEntity } from 'src/core/modules/domain/entities/base-filter.entity'; -export interface FilterUserPrivilegeEntity extends BaseFilterEntity {} +export type FilterUserPrivilegeEntity = BaseFilterEntity; diff --git a/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/managers/menu-user-privilege-configuration.manager.ts b/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/managers/menu-user-privilege-configuration.manager.ts new file mode 100644 index 0000000..cee263b --- /dev/null +++ b/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/managers/menu-user-privilege-configuration.manager.ts @@ -0,0 +1,131 @@ +import { Injectable } from '@nestjs/common'; +import { UserPrivilegeConfigurationEntity } from '../../../entities/user-privilege-configuration.entity'; +import { + Param, + RelationParam, +} from 'src/core/modules/domain/entities/base-filter.entity'; +import { BaseIndexManager } from 'src/core/modules/domain/usecase/managers/base-index.manager'; +import { ORDER_TYPE } from 'src/core/strings/constants/base.constants'; +import { Brackets, SelectQueryBuilder } from 'typeorm'; +import { FilterMenuUserPrivilegeConfigurationDto } from 'src/modules/user-related/user-privilege/infrastructure/dto/filter-menu-user-privilege-configuration.dto'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { UserRole } from 'src/modules/user-related/user/constants'; +import { UserPrivilegeConfigurationHelper } from '../helpers/generate-user-privilege-configuration.helper'; + +@Injectable() +export class MenuUserPrivilegeConfigurationManager extends BaseIndexManager { + async prepareData(): Promise { + Object.assign(this.filterParam, { + order_by: `${this.tableName}.index`, + order_type: ORDER_TYPE.ASC, + limit: 100, + }); + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + joinRelations: [], + selectRelations: [], + countRelations: [], + }; + } + + get selects(): string[] { + return []; + } + + get specificFilter(): Param[] { + return []; + } + + setQueryFilter( + queryBuilder: SelectQueryBuilder, + ): SelectQueryBuilder { + if (this.filterParam.modules) { + queryBuilder.andWhere('module In (:...modules)', { + modules: this.filterParam.modules, + }); + } + + if (this.filterParam.menus) { + queryBuilder.andWhere('menu In (:...menus)', { + menus: this.filterParam.menus, + }); + } + + queryBuilder.andWhere('user_privilege_id = :user_privilege_id', { + user_privilege_id: this.userProvider.user.user_privilege_id, + }); + + queryBuilder.andWhere( + new Brackets((qb) => { + qb.where('"view" = :view', { view: true }).orWhere( + '"create" = :create', + { + create: true, + }, + ); + }), + ); + + return queryBuilder; + } + + getMenuSuperAdmin(params: FilterMenuUserPrivilegeConfigurationDto) { + const modules = params?.modules ?? []; + const menus = params?.menus ?? []; + let configs = UserPrivilegeConfigurationHelper.createConfigurations(); + + if (modules.length > 0) { + configs = configs.filter((item) => modules.includes(item.module)); + } + + if (menus.length > 0) { + configs = configs.filter((item) => menus.includes(item.menu)); + } + + const newResult = configs.map((item) => { + const newItem = item; + + item.actions?.forEach((element) => { + item[element] = true; + }); + + newItem.actions = undefined; + + const cleanData = Object.entries(newItem) + .filter(([key, value]) => value !== undefined) + .reduce((obj, [key, value]) => { + obj[key] = value; + return obj; + }, {}); + + return cleanData; + }); + + return newResult as any; + } + + getResult(): PaginationResponse { + const role = this.userProvider.user.role; + const isSuperAdmin = role === UserRole.SUPERADMIN; + + if (isSuperAdmin) { + return { + data: this.getMenuSuperAdmin(this.filterParam), + total: 100, + }; + } + + return this.result; + } +} diff --git a/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/user-privilege-configuration-data.orchestrator.ts b/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/user-privilege-configuration-data.orchestrator.ts index 2e4488c..4dc4413 100644 --- a/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/user-privilege-configuration-data.orchestrator.ts +++ b/src/modules/user-related/user-privilege/domain/usecases/user-privilege-configuration/user-privilege-configuration-data.orchestrator.ts @@ -5,12 +5,14 @@ import { UserPrivilegeConfigurationService } from '../../../data/service/user-pr import { IndexUserPrivilegeConfigurationManager } from './managers/index-user-privilege-configuration.manager'; import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { MenuUserPrivilegeConfigurationManager } from './managers/menu-user-privilege-configuration.manager'; @Injectable() export class UserPrivilegeConfigurationDataOrchestrator { constructor( private updateManager: UpdateUserPrivilegeConfigurationManager, private indexManager: IndexUserPrivilegeConfigurationManager, + private privilegeMenuManager: MenuUserPrivilegeConfigurationManager, private serviceData: UserPrivilegeConfigurationService, ) {} @@ -35,4 +37,21 @@ export class UserPrivilegeConfigurationDataOrchestrator { await this.indexManager.execute(); return this.indexManager.getResult(); } + + async privilegeMenu( + params, + ): Promise> { + this.privilegeMenuManager.setFilterParam({ + page: 1, + limit: 100, + ...(params ?? {}), + }); + + this.privilegeMenuManager.setService( + this.serviceData, + TABLE_NAME.USER_PRIVILEGE_CONFIGURATION, + ); + await this.privilegeMenuManager.execute(); + return this.privilegeMenuManager.getResult(); + } } diff --git a/src/modules/user-related/user-privilege/domain/usecases/user-privilege/managers/create-user-privilege.manager.ts b/src/modules/user-related/user-privilege/domain/usecases/user-privilege/managers/create-user-privilege.manager.ts index 7fc5836..f0b8d1b 100644 --- a/src/modules/user-related/user-privilege/domain/usecases/user-privilege/managers/create-user-privilege.manager.ts +++ b/src/modules/user-related/user-privilege/domain/usecases/user-privilege/managers/create-user-privilege.manager.ts @@ -24,7 +24,9 @@ export class CreateUserPrivilegeManager extends BaseCreateManager {} + async generateConfig(): Promise { + // TODO: Implement logic here + } get uniqueColumns(): columnUniques[] { return [{ column: 'name' }]; diff --git a/src/modules/user-related/user-privilege/domain/usecases/user-privilege/user-privilege-data.orchestrator.ts b/src/modules/user-related/user-privilege/domain/usecases/user-privilege/user-privilege-data.orchestrator.ts index 9099073..31b7c67 100644 --- a/src/modules/user-related/user-privilege/domain/usecases/user-privilege/user-privilege-data.orchestrator.ts +++ b/src/modules/user-related/user-privilege/domain/usecases/user-privilege/user-privilege-data.orchestrator.ts @@ -49,7 +49,7 @@ export class UserPrivilegeDataOrchestrator extends BaseDataTransactionOrchestrat return this.updateManager.getResult(); } - async delete(dataId): Promise { + async delete(dataId): Promise { this.deleteManager.setData(dataId); this.deleteManager.setService(this.serviceData, TABLE_NAME.USER_PRIVILEGE); await this.deleteManager.execute(); @@ -66,7 +66,7 @@ export class UserPrivilegeDataOrchestrator extends BaseDataTransactionOrchestrat return this.batchDeleteManager.getResult(); } - async active(dataId): Promise { + async active(dataId): Promise { this.activeManager.setData(dataId, STATUS.ACTIVE); this.activeManager.setService(this.serviceData, TABLE_NAME.USER_PRIVILEGE); await this.activeManager.execute(); @@ -83,7 +83,7 @@ export class UserPrivilegeDataOrchestrator extends BaseDataTransactionOrchestrat return this.batchActiveManager.getResult(); } - async confirm(dataId): Promise { + async confirm(dataId): Promise { this.confirmManager.setData(dataId, STATUS.ACTIVE); this.confirmManager.setService(this.serviceData, TABLE_NAME.USER_PRIVILEGE); await this.confirmManager.execute(); @@ -100,7 +100,7 @@ export class UserPrivilegeDataOrchestrator extends BaseDataTransactionOrchestrat return this.batchConfirmManager.getResult(); } - async inactive(dataId): Promise { + async inactive(dataId): Promise { this.inactiveManager.setData(dataId, STATUS.INACTIVE); this.inactiveManager.setService( this.serviceData, diff --git a/src/modules/user-related/user-privilege/infrastructure/dto/filter-menu-user-privilege-configuration.dto.ts b/src/modules/user-related/user-privilege/infrastructure/dto/filter-menu-user-privilege-configuration.dto.ts new file mode 100644 index 0000000..f99213f --- /dev/null +++ b/src/modules/user-related/user-privilege/infrastructure/dto/filter-menu-user-privilege-configuration.dto.ts @@ -0,0 +1,16 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; + +export class FilterMenuUserPrivilegeConfigurationDto { + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + modules: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + menus: string[]; +} diff --git a/src/modules/user-related/user-privilege/infrastructure/user-privilege-configuration.controller.ts b/src/modules/user-related/user-privilege/infrastructure/user-privilege-configuration.controller.ts index fcfde04..684d7b5 100644 --- a/src/modules/user-related/user-privilege/infrastructure/user-privilege-configuration.controller.ts +++ b/src/modules/user-related/user-privilege/infrastructure/user-privilege-configuration.controller.ts @@ -1,6 +1,6 @@ import { Body, Controller, Get, Param, Put, Query } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; -import { Public } from 'src/core/guards'; +import { ExcludePrivilege, Public } from 'src/core/guards'; import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; import { UserPrivilegeConfigurationDataOrchestrator } from '../domain/usecases/user-privilege-configuration/user-privilege-configuration-data.orchestrator'; import { UserPrivilegeConfigurationDto } from './dto/user-privilege-configuration.dto'; @@ -8,6 +8,7 @@ import { UserPrivilegeConfigurationEntity } from '../domain/entities/user-privil import { FilterUserPrivilegeConfigurationDto } from './dto/filter-user-privilege-configuration.dto'; import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; import { Pagination } from 'src/core/response'; +import { FilterMenuUserPrivilegeConfigurationDto } from './dto/filter-menu-user-privilege-configuration.dto'; @ApiTags( `${MODULE_NAME.USER_PRIVILEGE_CONFIGURATION.split('-').join(' ')} - data`, @@ -34,4 +35,12 @@ export class UserPrivilegeConfigurationController { ): Promise> { return await this.orchestrator.index(params); } + + @Get('/menu') + @ExcludePrivilege() + async privilegeMenu( + @Query() params: FilterMenuUserPrivilegeConfigurationDto, + ): Promise { + return (await this.orchestrator.privilegeMenu(params)).data ?? []; + } } diff --git a/src/modules/user-related/user-privilege/infrastructure/user-privilege-data.controller.ts b/src/modules/user-related/user-privilege/infrastructure/user-privilege-data.controller.ts index 4f8089a..0efb974 100644 --- a/src/modules/user-related/user-privilege/infrastructure/user-privilege-data.controller.ts +++ b/src/modules/user-related/user-privilege/infrastructure/user-privilege-data.controller.ts @@ -36,7 +36,7 @@ export class UserPrivilegeDataController { } @Patch(':id/active') - async active(@Param('id') dataId: string): Promise { + async active(@Param('id') dataId: string): Promise { return await this.orchestrator.active(dataId); } @@ -46,7 +46,7 @@ export class UserPrivilegeDataController { } @Patch(':id/confirm') - async confirm(@Param('id') dataId: string): Promise { + async confirm(@Param('id') dataId: string): Promise { return await this.orchestrator.confirm(dataId); } @@ -56,7 +56,7 @@ export class UserPrivilegeDataController { } @Patch(':id/inactive') - async inactive(@Param('id') dataId: string): Promise { + async inactive(@Param('id') dataId: string): Promise { return await this.orchestrator.inactive(dataId); } @@ -74,7 +74,7 @@ export class UserPrivilegeDataController { } @Delete(':id') - async delete(@Param('id') dataId: string): Promise { + async delete(@Param('id') dataId: string): Promise { return await this.orchestrator.delete(dataId); } } diff --git a/src/modules/user-related/user-privilege/user-privilege.module.ts b/src/modules/user-related/user-privilege/user-privilege.module.ts index 5b82d7c..d8a52c5 100644 --- a/src/modules/user-related/user-privilege/user-privilege.module.ts +++ b/src/modules/user-related/user-privilege/user-privilege.module.ts @@ -27,6 +27,7 @@ import { UserPrivilegeConfigurationController } from './infrastructure/user-priv import { UserPrivilegeConfigurationDataOrchestrator } from './domain/usecases/user-privilege-configuration/user-privilege-configuration-data.orchestrator'; import { IndexUserPrivilegeConfigurationManager } from './domain/usecases/user-privilege-configuration/managers/index-user-privilege-configuration.manager'; import { UserPrivilegeModels } from './constants'; +import { MenuUserPrivilegeConfigurationManager } from './domain/usecases/user-privilege-configuration/managers/menu-user-privilege-configuration.manager'; @Module({ imports: [ @@ -55,6 +56,7 @@ import { UserPrivilegeModels } from './constants'; IndexUserPrivilegeConfigurationManager, UpdateUserPrivilegeConfigurationManager, + MenuUserPrivilegeConfigurationManager, UserPrivilegeDataService, UserPrivilegeReadService, diff --git a/src/modules/user-related/user/domain/usecases/managers/create-user.manager.ts b/src/modules/user-related/user/domain/usecases/managers/create-user.manager.ts index 8251a31..ba2c477 100644 --- a/src/modules/user-related/user/domain/usecases/managers/create-user.manager.ts +++ b/src/modules/user-related/user/domain/usecases/managers/create-user.manager.ts @@ -31,7 +31,9 @@ export class CreateUserManager extends BaseCreateManager { return; } - async generateConfig(): Promise {} + async generateConfig(): Promise { + // TODO: Implement logic here + } get validateRelations(): validateRelations[] { return []; diff --git a/src/modules/user-related/user/domain/usecases/user-data.orchestrator.ts b/src/modules/user-related/user/domain/usecases/user-data.orchestrator.ts index dec4731..b77e469 100644 --- a/src/modules/user-related/user/domain/usecases/user-data.orchestrator.ts +++ b/src/modules/user-related/user/domain/usecases/user-data.orchestrator.ts @@ -58,7 +58,7 @@ export class UserDataOrchestrator extends BaseDataTransactionOrchestrator { + async delete(dataId): Promise { this.deleteManager.setData(dataId); this.deleteManager.setService(this.serviceData, TABLE_NAME.USER); await this.deleteManager.execute(); @@ -72,7 +72,7 @@ export class UserDataOrchestrator extends BaseDataTransactionOrchestrator { + async active(dataId): Promise { this.activeManager.setData(dataId, STATUS.ACTIVE); this.activeManager.setService(this.serviceData, TABLE_NAME.USER); await this.activeManager.execute(); @@ -86,7 +86,7 @@ export class UserDataOrchestrator extends BaseDataTransactionOrchestrator { + async confirm(dataId): Promise { this.confirmManager.setData(dataId, STATUS.ACTIVE); this.confirmManager.setService(this.serviceData, TABLE_NAME.USER); await this.confirmManager.execute(); @@ -100,7 +100,7 @@ export class UserDataOrchestrator extends BaseDataTransactionOrchestrator { + async inactive(dataId): Promise { this.inactiveManager.setData(dataId, STATUS.INACTIVE); this.inactiveManager.setService(this.serviceData, TABLE_NAME.USER); await this.inactiveManager.execute(); diff --git a/src/modules/user-related/user/infrastructure/user-data.controller.ts b/src/modules/user-related/user/infrastructure/user-data.controller.ts index 5f322e7..6377695 100644 --- a/src/modules/user-related/user/infrastructure/user-data.controller.ts +++ b/src/modules/user-related/user/infrastructure/user-data.controller.ts @@ -36,7 +36,7 @@ export class UserDataController { } @Patch(':id/active') - async active(@Param('id') dataId: string): Promise { + async active(@Param('id') dataId: string): Promise { return await this.orchestrator.active(dataId); } @@ -46,7 +46,7 @@ export class UserDataController { } @Patch(':id/confirm') - async confirm(@Param('id') dataId: string): Promise { + async confirm(@Param('id') dataId: string): Promise { return await this.orchestrator.confirm(dataId); } @@ -56,7 +56,7 @@ export class UserDataController { } @Patch(':id/inactive') - async inactive(@Param('id') dataId: string): Promise { + async inactive(@Param('id') dataId: string): Promise { return await this.orchestrator.inactive(dataId); } @@ -82,7 +82,7 @@ export class UserDataController { } @Delete(':id') - async delete(@Param('id') dataId: string): Promise { + async delete(@Param('id') dataId: string): Promise { return await this.orchestrator.delete(dataId); } } diff --git a/src/modules/web-information/banner/banner.module.ts b/src/modules/web-information/banner/banner.module.ts new file mode 100644 index 0000000..61e54d8 --- /dev/null +++ b/src/modules/web-information/banner/banner.module.ts @@ -0,0 +1,54 @@ +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 { BannerDataService } from './data/services/banner-data.service'; +import { BannerReadService } from './data/services/banner-read.service'; +import { BannerReadController } from './infrastructure/banner-read.controller'; +import { BannerReadOrchestrator } from './domain/usecases/banner-read.orchestrator'; +import { BannerDataController } from './infrastructure/banner-data.controller'; +import { BannerDataOrchestrator } from './domain/usecases/banner-data.orchestrator'; +import { CreateBannerManager } from './domain/usecases/managers/create-banner.manager'; +import { CqrsModule } from '@nestjs/cqrs'; +import { IndexBannerManager } from './domain/usecases/managers/index-banner.manager'; +import { DeleteBannerManager } from './domain/usecases/managers/delete-banner.manager'; +import { UpdateBannerManager } from './domain/usecases/managers/update-banner.manager'; +import { ActiveBannerManager } from './domain/usecases/managers/active-banner.manager'; +import { ConfirmBannerManager } from './domain/usecases/managers/confirm-banner.manager'; +import { InactiveBannerManager } from './domain/usecases/managers/inactive-banner.manager'; +import { DetailBannerManager } from './domain/usecases/managers/detail-banner.manager'; +import { BatchDeleteBannerManager } from './domain/usecases/managers/batch-delete-banner.manager'; +import { BatchActiveBannerManager } from './domain/usecases/managers/batch-active-banner.manager'; +import { BatchConfirmBannerManager } from './domain/usecases/managers/batch-confirm-banner.manager'; +import { BatchInactiveBannerManager } from './domain/usecases/managers/batch-inactive-banner.manager'; +import { BannerModel } from './data/models/banner.model'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature([BannerModel], CONNECTION_NAME.DEFAULT), + CqrsModule, + ], + controllers: [BannerDataController, BannerReadController], + providers: [ + IndexBannerManager, + DetailBannerManager, + CreateBannerManager, + DeleteBannerManager, + UpdateBannerManager, + ActiveBannerManager, + ConfirmBannerManager, + InactiveBannerManager, + BatchDeleteBannerManager, + BatchActiveBannerManager, + BatchConfirmBannerManager, + BatchInactiveBannerManager, + + BannerDataService, + BannerReadService, + + BannerDataOrchestrator, + BannerReadOrchestrator, + ], +}) +export class BannerModule {} diff --git a/src/modules/web-information/banner/constants.ts b/src/modules/web-information/banner/constants.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/web-information/banner/data/models/banner.model.ts b/src/modules/web-information/banner/data/models/banner.model.ts new file mode 100644 index 0000000..f434b59 --- /dev/null +++ b/src/modules/web-information/banner/data/models/banner.model.ts @@ -0,0 +1,19 @@ +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { BannerEntity } from '../../domain/entities/banner.entity'; +import { Column, Entity } from 'typeorm'; +import { BaseStatusModel } from 'src/core/modules/data/model/base-status.model'; + +@Entity(TABLE_NAME.BANNER) +export class BannerModel + extends BaseStatusModel + implements BannerEntity +{ + @Column('varchar', { name: 'image_url', nullable: true }) + image_url: string; + + @Column('varchar', { name: 'title', nullable: true }) + title: string; + + @Column('varchar', { name: 'link', nullable: true }) + link: string; +} diff --git a/src/modules/web-information/banner/data/services/banner-data.service.ts b/src/modules/web-information/banner/data/services/banner-data.service.ts new file mode 100644 index 0000000..b42e3ae --- /dev/null +++ b/src/modules/web-information/banner/data/services/banner-data.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; +import { BannerEntity } from '../../domain/entities/banner.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { BannerModel } from '../models/banner.model'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { Repository } from 'typeorm'; + +@Injectable() +export class BannerDataService extends BaseDataService { + constructor( + @InjectRepository(BannerModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/web-information/banner/data/services/banner-read.service.ts b/src/modules/web-information/banner/data/services/banner-read.service.ts new file mode 100644 index 0000000..bc5e314 --- /dev/null +++ b/src/modules/web-information/banner/data/services/banner-read.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { BannerEntity } from '../../domain/entities/banner.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { BannerModel } from '../models/banner.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 BannerReadService extends BaseReadService { + constructor( + @InjectRepository(BannerModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/web-information/banner/domain/entities/banner.entity.ts b/src/modules/web-information/banner/domain/entities/banner.entity.ts new file mode 100644 index 0000000..fe2f1a7 --- /dev/null +++ b/src/modules/web-information/banner/domain/entities/banner.entity.ts @@ -0,0 +1,7 @@ +import { BaseStatusEntity } from 'src/core/modules/domain/entities/base-status.entity'; + +export interface BannerEntity extends BaseStatusEntity { + image_url: string; + title: string; + link: string; +} diff --git a/src/modules/web-information/banner/domain/entities/event/banner-change-status.event.ts b/src/modules/web-information/banner/domain/entities/event/banner-change-status.event.ts new file mode 100644 index 0000000..3c0467c --- /dev/null +++ b/src/modules/web-information/banner/domain/entities/event/banner-change-status.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class BannerChangeStatusEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/banner/domain/entities/event/banner-created.event.ts b/src/modules/web-information/banner/domain/entities/event/banner-created.event.ts new file mode 100644 index 0000000..816f1f1 --- /dev/null +++ b/src/modules/web-information/banner/domain/entities/event/banner-created.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class BannerCreatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/banner/domain/entities/event/banner-deleted.event.ts b/src/modules/web-information/banner/domain/entities/event/banner-deleted.event.ts new file mode 100644 index 0000000..d8dcdd6 --- /dev/null +++ b/src/modules/web-information/banner/domain/entities/event/banner-deleted.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class BannerDeletedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/banner/domain/entities/event/banner-updated.event.ts b/src/modules/web-information/banner/domain/entities/event/banner-updated.event.ts new file mode 100644 index 0000000..fc9bf1e --- /dev/null +++ b/src/modules/web-information/banner/domain/entities/event/banner-updated.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class BannerUpdatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/banner/domain/entities/filter-banner.entity.ts b/src/modules/web-information/banner/domain/entities/filter-banner.entity.ts new file mode 100644 index 0000000..a895e80 --- /dev/null +++ b/src/modules/web-information/banner/domain/entities/filter-banner.entity.ts @@ -0,0 +1,3 @@ +import { BaseFilterEntity } from 'src/core/modules/domain/entities/base-filter.entity'; + +export interface FilterBannerEntity extends BaseFilterEntity {} diff --git a/src/modules/web-information/banner/domain/usecases/banner-data.orchestrator.ts b/src/modules/web-information/banner/domain/usecases/banner-data.orchestrator.ts new file mode 100644 index 0000000..3116fb7 --- /dev/null +++ b/src/modules/web-information/banner/domain/usecases/banner-data.orchestrator.ts @@ -0,0 +1,106 @@ +import { Injectable } from '@nestjs/common'; +import { CreateBannerManager } from './managers/create-banner.manager'; +import { BannerDataService } from '../../data/services/banner-data.service'; +import { BannerEntity } from '../entities/banner.entity'; +import { DeleteBannerManager } from './managers/delete-banner.manager'; +import { UpdateBannerManager } from './managers/update-banner.manager'; +import { BaseDataTransactionOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-data-transaction.orchestrator'; +import { ActiveBannerManager } from './managers/active-banner.manager'; +import { InactiveBannerManager } from './managers/inactive-banner.manager'; +import { ConfirmBannerManager } from './managers/confirm-banner.manager'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchConfirmBannerManager } from './managers/batch-confirm-banner.manager'; +import { BatchInactiveBannerManager } from './managers/batch-inactive-banner.manager'; +import { BatchActiveBannerManager } from './managers/batch-active-banner.manager'; +import { BatchDeleteBannerManager } from './managers/batch-delete-banner.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@Injectable() +export class BannerDataOrchestrator extends BaseDataTransactionOrchestrator { + constructor( + private createManager: CreateBannerManager, + private updateManager: UpdateBannerManager, + private deleteManager: DeleteBannerManager, + private activeManager: ActiveBannerManager, + private confirmManager: ConfirmBannerManager, + private inactiveManager: InactiveBannerManager, + private batchDeleteManager: BatchDeleteBannerManager, + private batchActiveManager: BatchActiveBannerManager, + private batchConfirmManager: BatchConfirmBannerManager, + private batchInactiveManager: BatchInactiveBannerManager, + private serviceData: BannerDataService, + ) { + super(); + } + + async create(data): Promise { + this.createManager.setData(data); + this.createManager.setService(this.serviceData, TABLE_NAME.BANNER); + await this.createManager.execute(); + return this.createManager.getResult(); + } + + async update(dataId, data): Promise { + this.updateManager.setData(dataId, data); + this.updateManager.setService(this.serviceData, TABLE_NAME.BANNER); + await this.updateManager.execute(); + return this.updateManager.getResult(); + } + + async delete(dataId): Promise { + this.deleteManager.setData(dataId); + this.deleteManager.setService(this.serviceData, TABLE_NAME.BANNER); + await this.deleteManager.execute(); + return this.deleteManager.getResult(); + } + + async batchDelete(dataIds: string[]): Promise { + this.batchDeleteManager.setData(dataIds); + this.batchDeleteManager.setService(this.serviceData, TABLE_NAME.BANNER); + 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.BANNER); + 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.BANNER); + 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.BANNER); + 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.BANNER); + 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.BANNER); + 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.BANNER); + await this.batchInactiveManager.execute(); + return this.batchInactiveManager.getResult(); + } +} diff --git a/src/modules/web-information/banner/domain/usecases/banner-read.orchestrator.ts b/src/modules/web-information/banner/domain/usecases/banner-read.orchestrator.ts new file mode 100644 index 0000000..7ec3cb4 --- /dev/null +++ b/src/modules/web-information/banner/domain/usecases/banner-read.orchestrator.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@nestjs/common'; +import { IndexBannerManager } from './managers/index-banner.manager'; +import { BannerReadService } from '../../data/services/banner-read.service'; +import { BannerEntity } from '../entities/banner.entity'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { BaseReadOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-read.orchestrator'; +import { DetailBannerManager } from './managers/detail-banner.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@Injectable() +export class BannerReadOrchestrator extends BaseReadOrchestrator { + constructor( + private indexManager: IndexBannerManager, + private detailManager: DetailBannerManager, + private serviceData: BannerReadService, + ) { + super(); + } + + async index(params): Promise> { + this.indexManager.setFilterParam(params); + this.indexManager.setService(this.serviceData, TABLE_NAME.BANNER); + await this.indexManager.execute(); + return this.indexManager.getResult(); + } + + async detail(dataId: string): Promise { + this.detailManager.setData(dataId); + this.detailManager.setService(this.serviceData, TABLE_NAME.BANNER); + await this.detailManager.execute(); + return this.detailManager.getResult(); + } +} diff --git a/src/modules/web-information/banner/domain/usecases/managers/active-banner.manager.ts b/src/modules/web-information/banner/domain/usecases/managers/active-banner.manager.ts new file mode 100644 index 0000000..b46bce5 --- /dev/null +++ b/src/modules/web-information/banner/domain/usecases/managers/active-banner.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 { BannerEntity } from '../../entities/banner.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { BannerModel } from '../../../data/models/banner.model'; +import { BannerChangeStatusEvent } from '../../entities/event/banner-change-status.event'; + +@Injectable() +export class ActiveBannerManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.title}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return BannerModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: BannerChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/banner/domain/usecases/managers/batch-active-banner.manager.ts b/src/modules/web-information/banner/domain/usecases/managers/batch-active-banner.manager.ts new file mode 100644 index 0000000..f62fa04 --- /dev/null +++ b/src/modules/web-information/banner/domain/usecases/managers/batch-active-banner.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { BannerEntity } from '../../entities/banner.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { BannerModel } from '../../../data/models/banner.model'; +import { BannerChangeStatusEvent } from '../../entities/event/banner-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchActiveBannerManager extends BaseBatchUpdateStatusManager { + validateData(data: BannerEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return BannerModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: BannerChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/banner/domain/usecases/managers/batch-confirm-banner.manager.ts b/src/modules/web-information/banner/domain/usecases/managers/batch-confirm-banner.manager.ts new file mode 100644 index 0000000..c378071 --- /dev/null +++ b/src/modules/web-information/banner/domain/usecases/managers/batch-confirm-banner.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { BannerEntity } from '../../entities/banner.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { BannerModel } from '../../../data/models/banner.model'; +import { BannerChangeStatusEvent } from '../../entities/event/banner-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchConfirmBannerManager extends BaseBatchUpdateStatusManager { + validateData(data: BannerEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return BannerModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: BannerChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/banner/domain/usecases/managers/batch-delete-banner.manager.ts b/src/modules/web-information/banner/domain/usecases/managers/batch-delete-banner.manager.ts new file mode 100644 index 0000000..d29870c --- /dev/null +++ b/src/modules/web-information/banner/domain/usecases/managers/batch-delete-banner.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchDeleteManager } from 'src/core/modules/domain/usecase/managers/base-batch-delete.manager'; +import { BannerEntity } from '../../entities/banner.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { BannerModel } from '../../../data/models/banner.model'; +import { BannerDeletedEvent } from '../../entities/event/banner-deleted.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchDeleteBannerManager extends BaseBatchDeleteManager { + async beforeProcess(): Promise { + return; + } + + async validateData(data: BannerEntity): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return BannerModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: BannerDeletedEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/banner/domain/usecases/managers/batch-inactive-banner.manager.ts b/src/modules/web-information/banner/domain/usecases/managers/batch-inactive-banner.manager.ts new file mode 100644 index 0000000..3cb261b --- /dev/null +++ b/src/modules/web-information/banner/domain/usecases/managers/batch-inactive-banner.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { BannerEntity } from '../../entities/banner.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { BannerModel } from '../../../data/models/banner.model'; +import { BannerChangeStatusEvent } from '../../entities/event/banner-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchInactiveBannerManager extends BaseBatchUpdateStatusManager { + validateData(data: BannerEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return BannerModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: BannerChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/banner/domain/usecases/managers/confirm-banner.manager.ts b/src/modules/web-information/banner/domain/usecases/managers/confirm-banner.manager.ts new file mode 100644 index 0000000..5564e64 --- /dev/null +++ b/src/modules/web-information/banner/domain/usecases/managers/confirm-banner.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 { BannerEntity } from '../../entities/banner.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { BannerModel } from '../../../data/models/banner.model'; +import { BannerChangeStatusEvent } from '../../entities/event/banner-change-status.event'; + +@Injectable() +export class ConfirmBannerManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.title}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return BannerModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: BannerChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/banner/domain/usecases/managers/create-banner.manager.ts b/src/modules/web-information/banner/domain/usecases/managers/create-banner.manager.ts new file mode 100644 index 0000000..167bf06 --- /dev/null +++ b/src/modules/web-information/banner/domain/usecases/managers/create-banner.manager.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@nestjs/common'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { BannerEntity } from '../../entities/banner.entity'; +import { BannerModel } from '../../../data/models/banner.model'; +import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager'; +import { BannerCreatedEvent } from '../../entities/event/banner-created.event'; + +@Injectable() +export class CreateBannerManager extends BaseCreateManager { + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: BannerCreatedEvent, + data: this.data, + }, + ]; + } + + get entityTarget(): any { + return BannerModel; + } +} diff --git a/src/modules/web-information/banner/domain/usecases/managers/delete-banner.manager.ts b/src/modules/web-information/banner/domain/usecases/managers/delete-banner.manager.ts new file mode 100644 index 0000000..9650928 --- /dev/null +++ b/src/modules/web-information/banner/domain/usecases/managers/delete-banner.manager.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDeleteManager } from 'src/core/modules/domain/usecase/managers/base-delete.manager'; +import { BannerEntity } from '../../entities/banner.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { BannerModel } from '../../../data/models/banner.model'; +import { BannerDeletedEvent } from '../../entities/event/banner-deleted.event'; + +@Injectable() +export class DeleteBannerManager 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 BannerModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: BannerDeletedEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/banner/domain/usecases/managers/detail-banner.manager.ts b/src/modules/web-information/banner/domain/usecases/managers/detail-banner.manager.ts new file mode 100644 index 0000000..7c4e0ab --- /dev/null +++ b/src/modules/web-information/banner/domain/usecases/managers/detail-banner.manager.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDetailManager } from 'src/core/modules/domain/usecase/managers/base-detail.manager'; +import { BannerEntity } from '../../entities/banner.entity'; +import { RelationParam } from 'src/core/modules/domain/entities/base-filter.entity'; + +@Injectable() +export class DetailBannerManager extends BaseDetailManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + // relation only join (for query purpose) + joinRelations: [], + + // relation join and select (relasi yang ingin ditampilkan), + selectRelations: [], + + // relation yang hanya ingin dihitung (akan return number) + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.created_at`, + `${this.tableName}.creator_name`, + `${this.tableName}.updated_at`, + `${this.tableName}.editor_name`, + + `${this.tableName}.status`, + `${this.tableName}.image_url`, + `${this.tableName}.title`, + `${this.tableName}.link`, + ]; + } + + get setFindProperties(): any { + return { + id: this.dataId, + }; + } +} diff --git a/src/modules/web-information/banner/domain/usecases/managers/inactive-banner.manager.ts b/src/modules/web-information/banner/domain/usecases/managers/inactive-banner.manager.ts new file mode 100644 index 0000000..b8a886d --- /dev/null +++ b/src/modules/web-information/banner/domain/usecases/managers/inactive-banner.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 { BannerEntity } from '../../entities/banner.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { BannerModel } from '../../../data/models/banner.model'; +import { BannerChangeStatusEvent } from '../../entities/event/banner-change-status.event'; + +@Injectable() +export class InactiveBannerManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success inactive data ${this.result.title}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return BannerModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: BannerChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/banner/domain/usecases/managers/index-banner.manager.ts b/src/modules/web-information/banner/domain/usecases/managers/index-banner.manager.ts new file mode 100644 index 0000000..8e33166 --- /dev/null +++ b/src/modules/web-information/banner/domain/usecases/managers/index-banner.manager.ts @@ -0,0 +1,66 @@ +import { Injectable } from '@nestjs/common'; +import { BaseIndexManager } from 'src/core/modules/domain/usecase/managers/base-index.manager'; +import { BannerEntity } from '../../entities/banner.entity'; +import { SelectQueryBuilder } from 'typeorm'; +import { + Param, + RelationParam, +} from 'src/core/modules/domain/entities/base-filter.entity'; + +@Injectable() +export class IndexBannerManager extends BaseIndexManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + // relation only join (for query purpose) + joinRelations: [], + + // relation join and select (relasi yang ingin ditampilkan), + selectRelations: [], + + // relation yang hanya ingin dihitung (akan return number) + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.created_at`, + `${this.tableName}.creator_name`, + `${this.tableName}.updated_at`, + `${this.tableName}.editor_name`, + + `${this.tableName}.status`, + `${this.tableName}.image_url`, + `${this.tableName}.title`, + `${this.tableName}.link`, + ]; + } + + get specificFilter(): Param[] { + return [ + { + cols: `${this.tableName}.name`, + data: this.filterParam.names, + }, + ]; + } + + setQueryFilter( + queryBuilder: SelectQueryBuilder, + ): SelectQueryBuilder { + return queryBuilder; + } +} diff --git a/src/modules/web-information/banner/domain/usecases/managers/update-banner.manager.ts b/src/modules/web-information/banner/domain/usecases/managers/update-banner.manager.ts new file mode 100644 index 0000000..30b0c8b --- /dev/null +++ b/src/modules/web-information/banner/domain/usecases/managers/update-banner.manager.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@nestjs/common'; +import { BaseUpdateManager } from 'src/core/modules/domain/usecase/managers/base-update.manager'; +import { BannerEntity } from '../../entities/banner.entity'; +import { BannerModel } from '../../../data/models/banner.model'; +import { BannerUpdatedEvent } from '../../entities/event/banner-updated.event'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; + +@Injectable() +export class UpdateBannerManager extends BaseUpdateManager { + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get entityTarget(): any { + return BannerModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: BannerUpdatedEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/banner/index.ts b/src/modules/web-information/banner/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/web-information/banner/infrastructure/banner-data.controller.ts b/src/modules/web-information/banner/infrastructure/banner-data.controller.ts new file mode 100644 index 0000000..93b7f47 --- /dev/null +++ b/src/modules/web-information/banner/infrastructure/banner-data.controller.ts @@ -0,0 +1,78 @@ +import { + Body, + Controller, + Delete, + Param, + Patch, + Post, + Put, +} from '@nestjs/common'; +import { BannerDataOrchestrator } from '../domain/usecases/banner-data.orchestrator'; +import { BannerDto } from './dto/banner.dto'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { BannerEntity } from '../domain/entities/banner.entity'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto'; +import { Public } from 'src/core/guards'; + +@ApiTags(`${MODULE_NAME.BANNER.split('-').join(' ')} - data`) +@Controller(`v1/${MODULE_NAME.BANNER}`) +@Public(false) +@ApiBearerAuth('JWT') +export class BannerDataController { + constructor(private orchestrator: BannerDataOrchestrator) {} + + @Post() + async create(@Body() data: BannerDto): 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: BannerDto, + ): Promise { + return await this.orchestrator.update(dataId, data); + } + + @Delete(':id') + async delete(@Param('id') dataId: string): Promise { + return await this.orchestrator.delete(dataId); + } +} diff --git a/src/modules/web-information/banner/infrastructure/banner-read.controller.ts b/src/modules/web-information/banner/infrastructure/banner-read.controller.ts new file mode 100644 index 0000000..62ae8aa --- /dev/null +++ b/src/modules/web-information/banner/infrastructure/banner-read.controller.ts @@ -0,0 +1,30 @@ +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { FilterBannerDto } from './dto/filter-banner.dto'; +import { Pagination } from 'src/core/response'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { BannerEntity } from '../domain/entities/banner.entity'; +import { BannerReadOrchestrator } from '../domain/usecases/banner-read.orchestrator'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { Public } from 'src/core/guards'; + +@ApiTags(`${MODULE_NAME.BANNER.split('-').join(' ')} - read`) +@Controller(`v1/${MODULE_NAME.BANNER}`) +@Public(false) +@ApiBearerAuth('JWT') +export class BannerReadController { + constructor(private orchestrator: BannerReadOrchestrator) {} + + @Get() + @Pagination() + async index( + @Query() params: FilterBannerDto, + ): Promise> { + return await this.orchestrator.index(params); + } + + @Get(':id') + async detail(@Param('id') id: string): Promise { + return await this.orchestrator.detail(id); + } +} diff --git a/src/modules/web-information/banner/infrastructure/dto/banner.dto.ts b/src/modules/web-information/banner/infrastructure/dto/banner.dto.ts new file mode 100644 index 0000000..bb055b4 --- /dev/null +++ b/src/modules/web-information/banner/infrastructure/dto/banner.dto.ts @@ -0,0 +1,26 @@ +import { BaseStatusDto } from 'src/core/modules/infrastructure/dto/base-status.dto'; +import { BannerEntity } from '../../domain/entities/banner.entity'; +import { ApiProperty } from '@nestjs/swagger'; + +export class BannerDto extends BaseStatusDto implements BannerEntity { + @ApiProperty({ + type: String, + required: false, + example: 'https://...', + }) + image_url: string; + + @ApiProperty({ + type: String, + required: true, + example: 'The Flash Speed Force', + }) + title: string; + + @ApiProperty({ + type: String, + required: false, + example: 'Get ready to take on the first ride-within-a-ride experience', + }) + link: string; +} diff --git a/src/modules/web-information/banner/infrastructure/dto/filter-banner.dto.ts b/src/modules/web-information/banner/infrastructure/dto/filter-banner.dto.ts new file mode 100644 index 0000000..b8fa4af --- /dev/null +++ b/src/modules/web-information/banner/infrastructure/dto/filter-banner.dto.ts @@ -0,0 +1,6 @@ +import { BaseFilterDto } from 'src/core/modules/infrastructure/dto/base-filter.dto'; +import { FilterBannerEntity } from '../../domain/entities/filter-banner.entity'; + +export class FilterBannerDto + extends BaseFilterDto + implements FilterBannerEntity {} diff --git a/src/modules/web-information/faq/constants.ts b/src/modules/web-information/faq/constants.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/web-information/faq/data/models/faq.model.ts b/src/modules/web-information/faq/data/models/faq.model.ts new file mode 100644 index 0000000..2027efd --- /dev/null +++ b/src/modules/web-information/faq/data/models/faq.model.ts @@ -0,0 +1,16 @@ +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { FaqEntity } from '../../domain/entities/faq.entity'; +import { Column, Entity } from 'typeorm'; +import { BaseStatusModel } from 'src/core/modules/data/model/base-status.model'; + +@Entity(TABLE_NAME.FAQ) +export class FaqModel extends BaseStatusModel implements FaqEntity { + @Column('int', { name: 'sort_order', default: 0 }) + sort_order: number; + + @Column('varchar', { name: 'title', nullable: true }) + title: string; + + @Column('text', { name: 'description', nullable: true }) + description: string; +} diff --git a/src/modules/web-information/faq/data/services/faq-data.service.ts b/src/modules/web-information/faq/data/services/faq-data.service.ts new file mode 100644 index 0000000..1327784 --- /dev/null +++ b/src/modules/web-information/faq/data/services/faq-data.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; +import { FaqEntity } from '../../domain/entities/faq.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { FaqModel } from '../models/faq.model'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { Repository } from 'typeorm'; + +@Injectable() +export class FaqDataService extends BaseDataService { + constructor( + @InjectRepository(FaqModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } + + async getSortColumn(): Promise { + const query = this.repo.createQueryBuilder('data'); + + const sortColumn = await query + .select('data.sort_order') + .orderBy('data.sort_order', 'DESC') + .getOne(); + + const lastColumn = sortColumn?.sort_order ?? 0; + return lastColumn + 1; + } +} diff --git a/src/modules/web-information/faq/data/services/faq-read.service.ts b/src/modules/web-information/faq/data/services/faq-read.service.ts new file mode 100644 index 0000000..cfd09be --- /dev/null +++ b/src/modules/web-information/faq/data/services/faq-read.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { FaqEntity } from '../../domain/entities/faq.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { FaqModel } from '../models/faq.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 FaqReadService extends BaseReadService { + constructor( + @InjectRepository(FaqModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/web-information/faq/domain/entities/event/faq-change-status.event.ts b/src/modules/web-information/faq/domain/entities/event/faq-change-status.event.ts new file mode 100644 index 0000000..5694e39 --- /dev/null +++ b/src/modules/web-information/faq/domain/entities/event/faq-change-status.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class FaqChangeStatusEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/faq/domain/entities/event/faq-created.event.ts b/src/modules/web-information/faq/domain/entities/event/faq-created.event.ts new file mode 100644 index 0000000..7e8178c --- /dev/null +++ b/src/modules/web-information/faq/domain/entities/event/faq-created.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class FaqCreatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/faq/domain/entities/event/faq-deleted.event.ts b/src/modules/web-information/faq/domain/entities/event/faq-deleted.event.ts new file mode 100644 index 0000000..07985e1 --- /dev/null +++ b/src/modules/web-information/faq/domain/entities/event/faq-deleted.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class FaqDeletedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/faq/domain/entities/event/faq-updated.event.ts b/src/modules/web-information/faq/domain/entities/event/faq-updated.event.ts new file mode 100644 index 0000000..c48de05 --- /dev/null +++ b/src/modules/web-information/faq/domain/entities/event/faq-updated.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class FaqUpdatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/faq/domain/entities/faq.entity.ts b/src/modules/web-information/faq/domain/entities/faq.entity.ts new file mode 100644 index 0000000..80bff41 --- /dev/null +++ b/src/modules/web-information/faq/domain/entities/faq.entity.ts @@ -0,0 +1,7 @@ +import { BaseStatusEntity } from 'src/core/modules/domain/entities/base-status.entity'; + +export interface FaqEntity extends BaseStatusEntity { + sort_order: number; + title: string; + description: string; +} diff --git a/src/modules/web-information/faq/domain/entities/filter-faq.entity.ts b/src/modules/web-information/faq/domain/entities/filter-faq.entity.ts new file mode 100644 index 0000000..d0589ac --- /dev/null +++ b/src/modules/web-information/faq/domain/entities/filter-faq.entity.ts @@ -0,0 +1,5 @@ +import { BaseFilterEntity } from 'src/core/modules/domain/entities/base-filter.entity'; + +export interface FilterFaqEntity extends BaseFilterEntity { + titles: string[]; +} diff --git a/src/modules/web-information/faq/domain/usecases/faq-data.orchestrator.ts b/src/modules/web-information/faq/domain/usecases/faq-data.orchestrator.ts new file mode 100644 index 0000000..46e2d6d --- /dev/null +++ b/src/modules/web-information/faq/domain/usecases/faq-data.orchestrator.ts @@ -0,0 +1,115 @@ +import { Injectable } from '@nestjs/common'; +import { CreateFaqManager } from './managers/create-faq.manager'; +import { FaqDataService } from '../../data/services/faq-data.service'; +import { FaqEntity } from '../entities/faq.entity'; +import { DeleteFaqManager } from './managers/delete-faq.manager'; +import { UpdateFaqManager } from './managers/update-faq.manager'; +import { BaseDataTransactionOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-data-transaction.orchestrator'; +import { ActiveFaqManager } from './managers/active-faq.manager'; +import { InactiveFaqManager } from './managers/inactive-faq.manager'; +import { ConfirmFaqManager } from './managers/confirm-faq.manager'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchConfirmFaqManager } from './managers/batch-confirm-faq.manager'; +import { BatchInactiveFaqManager } from './managers/batch-inactive-faq.manager'; +import { BatchActiveFaqManager } from './managers/batch-active-faq.manager'; +import { BatchDeleteFaqManager } from './managers/batch-delete-faq.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { ChangePositionFaqManager } from './managers/change-position-faq.manager'; + +@Injectable() +export class FaqDataOrchestrator extends BaseDataTransactionOrchestrator { + constructor( + private createManager: CreateFaqManager, + private updateManager: UpdateFaqManager, + private deleteManager: DeleteFaqManager, + private activeManager: ActiveFaqManager, + private confirmManager: ConfirmFaqManager, + private inactiveManager: InactiveFaqManager, + private batchDeleteManager: BatchDeleteFaqManager, + private batchActiveManager: BatchActiveFaqManager, + private batchConfirmManager: BatchConfirmFaqManager, + private batchInactiveManager: BatchInactiveFaqManager, + private changePositionManager: ChangePositionFaqManager, + private serviceData: FaqDataService, + ) { + super(); + } + + async changePostion(data): Promise { + this.changePositionManager.setData(data, 'sort_order'); + this.changePositionManager.setService(this.serviceData, TABLE_NAME.FAQ); + await this.changePositionManager.execute(); + return this.changePositionManager.getResult(); + } + + async create(data): Promise { + this.createManager.setData(data); + this.createManager.setService(this.serviceData, TABLE_NAME.FAQ); + await this.createManager.execute(); + return this.createManager.getResult(); + } + + async update(dataId, data): Promise { + this.updateManager.setData(dataId, data); + this.updateManager.setService(this.serviceData, TABLE_NAME.FAQ); + await this.updateManager.execute(); + return this.updateManager.getResult(); + } + + async delete(dataId): Promise { + this.deleteManager.setData(dataId); + this.deleteManager.setService(this.serviceData, TABLE_NAME.FAQ); + await this.deleteManager.execute(); + return this.deleteManager.getResult(); + } + + async batchDelete(dataIds: string[]): Promise { + this.batchDeleteManager.setData(dataIds); + this.batchDeleteManager.setService(this.serviceData, TABLE_NAME.FAQ); + 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.FAQ); + 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.FAQ); + 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.FAQ); + 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.FAQ); + 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.FAQ); + 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.FAQ); + await this.batchInactiveManager.execute(); + return this.batchInactiveManager.getResult(); + } +} diff --git a/src/modules/web-information/faq/domain/usecases/faq-read.orchestrator.ts b/src/modules/web-information/faq/domain/usecases/faq-read.orchestrator.ts new file mode 100644 index 0000000..02e11cb --- /dev/null +++ b/src/modules/web-information/faq/domain/usecases/faq-read.orchestrator.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@nestjs/common'; +import { IndexFaqManager } from './managers/index-faq.manager'; +import { FaqReadService } from '../../data/services/faq-read.service'; +import { FaqEntity } from '../entities/faq.entity'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { BaseReadOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-read.orchestrator'; +import { DetailFaqManager } from './managers/detail-faq.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@Injectable() +export class FaqReadOrchestrator extends BaseReadOrchestrator { + constructor( + private indexManager: IndexFaqManager, + private detailManager: DetailFaqManager, + private serviceData: FaqReadService, + ) { + super(); + } + + async index(params): Promise> { + this.indexManager.setFilterParam(params); + this.indexManager.setService(this.serviceData, TABLE_NAME.FAQ); + await this.indexManager.execute(); + return this.indexManager.getResult(); + } + + async detail(dataId: string): Promise { + this.detailManager.setData(dataId); + this.detailManager.setService(this.serviceData, TABLE_NAME.FAQ); + await this.detailManager.execute(); + return this.detailManager.getResult(); + } +} diff --git a/src/modules/web-information/faq/domain/usecases/managers/active-faq.manager.ts b/src/modules/web-information/faq/domain/usecases/managers/active-faq.manager.ts new file mode 100644 index 0000000..c33c3bb --- /dev/null +++ b/src/modules/web-information/faq/domain/usecases/managers/active-faq.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 { FaqEntity } from '../../entities/faq.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { FaqModel } from '../../../data/models/faq.model'; +import { FaqChangeStatusEvent } from '../../entities/event/faq-change-status.event'; + +@Injectable() +export class ActiveFaqManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.title}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return FaqModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: FaqChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/faq/domain/usecases/managers/batch-active-faq.manager.ts b/src/modules/web-information/faq/domain/usecases/managers/batch-active-faq.manager.ts new file mode 100644 index 0000000..f385801 --- /dev/null +++ b/src/modules/web-information/faq/domain/usecases/managers/batch-active-faq.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { FaqEntity } from '../../entities/faq.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { FaqModel } from '../../../data/models/faq.model'; +import { FaqChangeStatusEvent } from '../../entities/event/faq-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchActiveFaqManager extends BaseBatchUpdateStatusManager { + validateData(data: FaqEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return FaqModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: FaqChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/faq/domain/usecases/managers/batch-confirm-faq.manager.ts b/src/modules/web-information/faq/domain/usecases/managers/batch-confirm-faq.manager.ts new file mode 100644 index 0000000..4c0bc93 --- /dev/null +++ b/src/modules/web-information/faq/domain/usecases/managers/batch-confirm-faq.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { FaqEntity } from '../../entities/faq.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { FaqModel } from '../../../data/models/faq.model'; +import { FaqChangeStatusEvent } from '../../entities/event/faq-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchConfirmFaqManager extends BaseBatchUpdateStatusManager { + validateData(data: FaqEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return FaqModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: FaqChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/faq/domain/usecases/managers/batch-delete-faq.manager.ts b/src/modules/web-information/faq/domain/usecases/managers/batch-delete-faq.manager.ts new file mode 100644 index 0000000..5476e9a --- /dev/null +++ b/src/modules/web-information/faq/domain/usecases/managers/batch-delete-faq.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchDeleteManager } from 'src/core/modules/domain/usecase/managers/base-batch-delete.manager'; +import { FaqEntity } from '../../entities/faq.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { FaqModel } from '../../../data/models/faq.model'; +import { FaqDeletedEvent } from '../../entities/event/faq-deleted.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchDeleteFaqManager extends BaseBatchDeleteManager { + async beforeProcess(): Promise { + return; + } + + async validateData(data: FaqEntity): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return FaqModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: FaqDeletedEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/faq/domain/usecases/managers/batch-inactive-faq.manager.ts b/src/modules/web-information/faq/domain/usecases/managers/batch-inactive-faq.manager.ts new file mode 100644 index 0000000..c587411 --- /dev/null +++ b/src/modules/web-information/faq/domain/usecases/managers/batch-inactive-faq.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { FaqEntity } from '../../entities/faq.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { FaqModel } from '../../../data/models/faq.model'; +import { FaqChangeStatusEvent } from '../../entities/event/faq-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchInactiveFaqManager extends BaseBatchUpdateStatusManager { + validateData(data: FaqEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return FaqModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: FaqChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/faq/domain/usecases/managers/change-position-faq.manager.ts b/src/modules/web-information/faq/domain/usecases/managers/change-position-faq.manager.ts new file mode 100644 index 0000000..ed717cd --- /dev/null +++ b/src/modules/web-information/faq/domain/usecases/managers/change-position-faq.manager.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@nestjs/common'; +import { BaseChangePosition } from 'src/core/modules/domain/usecase/managers/base-change-position.manager'; +import { FaqEntity } from '../../entities/faq.entity'; +import { FaqModel } from '../../../data/models/faq.model'; + +@Injectable() +export class ChangePositionFaqManager extends BaseChangePosition { + get entityTarget(): any { + return FaqModel; + } + + async afterProcess(): Promise { + return; + } +} diff --git a/src/modules/web-information/faq/domain/usecases/managers/confirm-faq.manager.ts b/src/modules/web-information/faq/domain/usecases/managers/confirm-faq.manager.ts new file mode 100644 index 0000000..86b6c51 --- /dev/null +++ b/src/modules/web-information/faq/domain/usecases/managers/confirm-faq.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 { FaqEntity } from '../../entities/faq.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { FaqModel } from '../../../data/models/faq.model'; +import { FaqChangeStatusEvent } from '../../entities/event/faq-change-status.event'; + +@Injectable() +export class ConfirmFaqManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.title}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return FaqModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: FaqChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/faq/domain/usecases/managers/create-faq.manager.ts b/src/modules/web-information/faq/domain/usecases/managers/create-faq.manager.ts new file mode 100644 index 0000000..bc36684 --- /dev/null +++ b/src/modules/web-information/faq/domain/usecases/managers/create-faq.manager.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@nestjs/common'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { FaqEntity } from '../../entities/faq.entity'; +import { FaqModel } from '../../../data/models/faq.model'; +import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager'; +import { FaqCreatedEvent } from '../../entities/event/faq-created.event'; + +@Injectable() +export class CreateFaqManager extends BaseCreateManager { + async beforeProcess(): Promise { + const sortColumn = await this.dataService.getSortColumn(); + + Object.assign(this.data, { + sort_order: sortColumn, + }); + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: FaqCreatedEvent, + data: this.data, + }, + ]; + } + + get entityTarget(): any { + return FaqModel; + } +} diff --git a/src/modules/web-information/faq/domain/usecases/managers/delete-faq.manager.ts b/src/modules/web-information/faq/domain/usecases/managers/delete-faq.manager.ts new file mode 100644 index 0000000..da3ac12 --- /dev/null +++ b/src/modules/web-information/faq/domain/usecases/managers/delete-faq.manager.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDeleteManager } from 'src/core/modules/domain/usecase/managers/base-delete.manager'; +import { FaqEntity } from '../../entities/faq.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { FaqModel } from '../../../data/models/faq.model'; +import { FaqDeletedEvent } from '../../entities/event/faq-deleted.event'; + +@Injectable() +export class DeleteFaqManager 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 FaqModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: FaqDeletedEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/faq/domain/usecases/managers/detail-faq.manager.ts b/src/modules/web-information/faq/domain/usecases/managers/detail-faq.manager.ts new file mode 100644 index 0000000..a44ee48 --- /dev/null +++ b/src/modules/web-information/faq/domain/usecases/managers/detail-faq.manager.ts @@ -0,0 +1,51 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDetailManager } from 'src/core/modules/domain/usecase/managers/base-detail.manager'; +import { FaqEntity } from '../../entities/faq.entity'; +import { RelationParam } from 'src/core/modules/domain/entities/base-filter.entity'; + +@Injectable() +export class DetailFaqManager extends BaseDetailManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + // relation only join (for query purpose) + joinRelations: [], + + // relation join and select (relasi yang ingin ditampilkan), + selectRelations: [], + + // relation yang hanya ingin dihitung (akan return number) + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.status`, + `${this.tableName}.created_at`, + `${this.tableName}.creator_name`, + `${this.tableName}.updated_at`, + `${this.tableName}.editor_name`, + `${this.tableName}.title`, + `${this.tableName}.description`, + ]; + } + + get setFindProperties(): any { + return { + id: this.dataId, + }; + } +} diff --git a/src/modules/web-information/faq/domain/usecases/managers/inactive-faq.manager.ts b/src/modules/web-information/faq/domain/usecases/managers/inactive-faq.manager.ts new file mode 100644 index 0000000..2e02d12 --- /dev/null +++ b/src/modules/web-information/faq/domain/usecases/managers/inactive-faq.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 { FaqEntity } from '../../entities/faq.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { FaqModel } from '../../../data/models/faq.model'; +import { FaqChangeStatusEvent } from '../../entities/event/faq-change-status.event'; + +@Injectable() +export class InactiveFaqManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success inactive data ${this.result.title}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return FaqModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: FaqChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/faq/domain/usecases/managers/index-faq.manager.ts b/src/modules/web-information/faq/domain/usecases/managers/index-faq.manager.ts new file mode 100644 index 0000000..a9ced77 --- /dev/null +++ b/src/modules/web-information/faq/domain/usecases/managers/index-faq.manager.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@nestjs/common'; +import { BaseIndexManager } from 'src/core/modules/domain/usecase/managers/base-index.manager'; +import { FaqEntity } from '../../entities/faq.entity'; +import { SelectQueryBuilder } from 'typeorm'; +import { + Param, + RelationParam, +} from 'src/core/modules/domain/entities/base-filter.entity'; +import { ORDER_TYPE } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class IndexFaqManager extends BaseIndexManager { + async prepareData(): Promise { + this.filterParam.order_by = `${this.tableName}.sort_order`; + this.filterParam.order_type = ORDER_TYPE.ASC; + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + // relation only join (for query purpose) + joinRelations: [], + + // relation join and select (relasi yang ingin ditampilkan), + selectRelations: [], + + // relation yang hanya ingin dihitung (akan return number) + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.status`, + `${this.tableName}.sort_order`, + `${this.tableName}.created_at`, + `${this.tableName}.creator_name`, + `${this.tableName}.updated_at`, + `${this.tableName}.editor_name`, + `${this.tableName}.title`, + `${this.tableName}.description`, + ]; + } + + get specificFilter(): Param[] { + return [ + { + cols: `${this.tableName}.title`, + data: this.filterParam.titles, + }, + ]; + } + + setQueryFilter( + queryBuilder: SelectQueryBuilder, + ): SelectQueryBuilder { + return queryBuilder; + } +} diff --git a/src/modules/web-information/faq/domain/usecases/managers/update-faq.manager.ts b/src/modules/web-information/faq/domain/usecases/managers/update-faq.manager.ts new file mode 100644 index 0000000..17aef38 --- /dev/null +++ b/src/modules/web-information/faq/domain/usecases/managers/update-faq.manager.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@nestjs/common'; +import { BaseUpdateManager } from 'src/core/modules/domain/usecase/managers/base-update.manager'; +import { FaqEntity } from '../../entities/faq.entity'; +import { FaqModel } from '../../../data/models/faq.model'; +import { FaqUpdatedEvent } from '../../entities/event/faq-updated.event'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; + +@Injectable() +export class UpdateFaqManager extends BaseUpdateManager { + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get entityTarget(): any { + return FaqModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: FaqUpdatedEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/faq/faq.module.ts b/src/modules/web-information/faq/faq.module.ts new file mode 100644 index 0000000..dee08b1 --- /dev/null +++ b/src/modules/web-information/faq/faq.module.ts @@ -0,0 +1,56 @@ +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 { FaqDataService } from './data/services/faq-data.service'; +import { FaqReadService } from './data/services/faq-read.service'; +import { FaqReadController } from './infrastructure/faq-read.controller'; +import { FaqReadOrchestrator } from './domain/usecases/faq-read.orchestrator'; +import { FaqDataController } from './infrastructure/faq-data.controller'; +import { FaqDataOrchestrator } from './domain/usecases/faq-data.orchestrator'; +import { CreateFaqManager } from './domain/usecases/managers/create-faq.manager'; +import { CqrsModule } from '@nestjs/cqrs'; +import { IndexFaqManager } from './domain/usecases/managers/index-faq.manager'; +import { DeleteFaqManager } from './domain/usecases/managers/delete-faq.manager'; +import { UpdateFaqManager } from './domain/usecases/managers/update-faq.manager'; +import { ActiveFaqManager } from './domain/usecases/managers/active-faq.manager'; +import { ConfirmFaqManager } from './domain/usecases/managers/confirm-faq.manager'; +import { InactiveFaqManager } from './domain/usecases/managers/inactive-faq.manager'; +import { DetailFaqManager } from './domain/usecases/managers/detail-faq.manager'; +import { BatchDeleteFaqManager } from './domain/usecases/managers/batch-delete-faq.manager'; +import { BatchActiveFaqManager } from './domain/usecases/managers/batch-active-faq.manager'; +import { BatchConfirmFaqManager } from './domain/usecases/managers/batch-confirm-faq.manager'; +import { BatchInactiveFaqManager } from './domain/usecases/managers/batch-inactive-faq.manager'; +import { FaqModel } from './data/models/faq.model'; +import { ChangePositionFaqManager } from './domain/usecases/managers/change-position-faq.manager'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature([FaqModel], CONNECTION_NAME.DEFAULT), + CqrsModule, + ], + controllers: [FaqDataController, FaqReadController], + providers: [ + IndexFaqManager, + DetailFaqManager, + CreateFaqManager, + DeleteFaqManager, + UpdateFaqManager, + ActiveFaqManager, + ConfirmFaqManager, + InactiveFaqManager, + BatchDeleteFaqManager, + BatchActiveFaqManager, + BatchConfirmFaqManager, + BatchInactiveFaqManager, + ChangePositionFaqManager, + + FaqDataService, + FaqReadService, + + FaqDataOrchestrator, + FaqReadOrchestrator, + ], +}) +export class FaqModule {} diff --git a/src/modules/web-information/faq/index.ts b/src/modules/web-information/faq/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/web-information/faq/infrastructure/dto/faq.dto.ts b/src/modules/web-information/faq/infrastructure/dto/faq.dto.ts new file mode 100644 index 0000000..96cffc1 --- /dev/null +++ b/src/modules/web-information/faq/infrastructure/dto/faq.dto.ts @@ -0,0 +1,25 @@ +import { BaseStatusDto } from 'src/core/modules/infrastructure/dto/base-status.dto'; +import { FaqEntity } from '../../domain/entities/faq.entity'; +import { IsString } from 'class-validator'; +import { ApiProperty } from '@nestjs/swagger'; +import { Exclude } from 'class-transformer'; + +export class FaqDto extends BaseStatusDto implements FaqEntity { + @Exclude() + sort_order: number; + + @ApiProperty({ + type: String, + required: true, + example: 'Booking', + }) + @IsString() + title: string; + + @ApiProperty({ + type: String, + required: false, + example: 'Booking descs', + }) + description: string; +} diff --git a/src/modules/web-information/faq/infrastructure/dto/filter-faq.dto.ts b/src/modules/web-information/faq/infrastructure/dto/filter-faq.dto.ts new file mode 100644 index 0000000..028b7a1 --- /dev/null +++ b/src/modules/web-information/faq/infrastructure/dto/filter-faq.dto.ts @@ -0,0 +1,12 @@ +import { BaseFilterDto } from 'src/core/modules/infrastructure/dto/base-filter.dto'; +import { FilterFaqEntity } from '../../domain/entities/filter-faq.entity'; +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; + +export class FilterFaqDto extends BaseFilterDto implements FilterFaqEntity { + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + titles: string[]; +} diff --git a/src/modules/web-information/faq/infrastructure/faq-data.controller.ts b/src/modules/web-information/faq/infrastructure/faq-data.controller.ts new file mode 100644 index 0000000..9fe59fa --- /dev/null +++ b/src/modules/web-information/faq/infrastructure/faq-data.controller.ts @@ -0,0 +1,84 @@ +import { + Body, + Controller, + Delete, + Param, + Patch, + Post, + Put, +} from '@nestjs/common'; +import { FaqDataOrchestrator } from '../domain/usecases/faq-data.orchestrator'; +import { FaqDto } from './dto/faq.dto'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { FaqEntity } from '../domain/entities/faq.entity'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto'; +import { Public } from 'src/core/guards'; +import { ChangePositionDto } from 'src/core/modules/infrastructure/dto/base-change-position.dto'; + +@ApiTags(`${MODULE_NAME.FAQ.split('-').join(' ')} - data`) +@Controller(`v1/${MODULE_NAME.FAQ}`) +@Public(false) +@ApiBearerAuth('JWT') +export class FaqDataController { + constructor(private orchestrator: FaqDataOrchestrator) {} + + @Post() + async create(@Body() data: FaqDto): Promise { + return await this.orchestrator.create(data); + } + + @Post('/change-position') + async dragDrop(@Body() body: ChangePositionDto): Promise { + return await this.orchestrator.changePostion(body); + } + + @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: FaqDto, + ): Promise { + return await this.orchestrator.update(dataId, data); + } + + @Delete(':id') + async delete(@Param('id') dataId: string): Promise { + return await this.orchestrator.delete(dataId); + } +} diff --git a/src/modules/web-information/faq/infrastructure/faq-read.controller.ts b/src/modules/web-information/faq/infrastructure/faq-read.controller.ts new file mode 100644 index 0000000..e282038 --- /dev/null +++ b/src/modules/web-information/faq/infrastructure/faq-read.controller.ts @@ -0,0 +1,30 @@ +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { FilterFaqDto } from './dto/filter-faq.dto'; +import { Pagination } from 'src/core/response'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { FaqEntity } from '../domain/entities/faq.entity'; +import { FaqReadOrchestrator } from '../domain/usecases/faq-read.orchestrator'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { Public } from 'src/core/guards'; + +@ApiTags(`${MODULE_NAME.FAQ.split('-').join(' ')} - read`) +@Controller(`v1/${MODULE_NAME.FAQ}`) +@Public(false) +@ApiBearerAuth('JWT') +export class FaqReadController { + constructor(private orchestrator: FaqReadOrchestrator) {} + + @Get() + @Pagination() + async index( + @Query() params: FilterFaqDto, + ): Promise> { + return await this.orchestrator.index(params); + } + + @Get(':id') + async detail(@Param('id') id: string): Promise { + return await this.orchestrator.detail(id); + } +} diff --git a/src/modules/web-information/gate/constants.ts b/src/modules/web-information/gate/constants.ts new file mode 100644 index 0000000..ee23e1b --- /dev/null +++ b/src/modules/web-information/gate/constants.ts @@ -0,0 +1,4 @@ +export enum GateType { + GATE_IN = 'gate masuk', + GATE_OUT = 'gate keluar', +} diff --git a/src/modules/web-information/gate/data/models/gate.model.ts b/src/modules/web-information/gate/data/models/gate.model.ts new file mode 100644 index 0000000..0940d28 --- /dev/null +++ b/src/modules/web-information/gate/data/models/gate.model.ts @@ -0,0 +1,34 @@ +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { GateEntity } from '../../domain/entities/gate.entity'; +import { Column, Entity, JoinColumn, ManyToOne } from 'typeorm'; +import { BaseStatusModel } from 'src/core/modules/data/model/base-status.model'; +import { GateType } from '../../constants'; +import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'; + +@Entity(TABLE_NAME.GATE) +export class GateModel + extends BaseStatusModel + implements GateEntity +{ + @Column('enum', { + name: 'type', + enum: GateType, + default: GateType.GATE_IN, + }) + type: GateType; + + @Column('varchar', { name: 'code', nullable: true }) + code: string; + + @Column('text', { name: 'note', nullable: true }) + note: string; + + @Column('varchar', { name: 'item_id', nullable: true }) + item_id: string; + @ManyToOne(() => ItemModel, (model) => model.gates, { + onDelete: 'CASCADE', + onUpdate: 'CASCADE', + }) + @JoinColumn({ name: 'item_id' }) + item: ItemModel; +} diff --git a/src/modules/web-information/gate/data/services/gate-data.service.ts b/src/modules/web-information/gate/data/services/gate-data.service.ts new file mode 100644 index 0000000..0e9c015 --- /dev/null +++ b/src/modules/web-information/gate/data/services/gate-data.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; +import { GateEntity } from '../../domain/entities/gate.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { GateModel } from '../models/gate.model'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { Repository } from 'typeorm'; + +@Injectable() +export class GateDataService extends BaseDataService { + constructor( + @InjectRepository(GateModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/web-information/gate/data/services/gate-read.service.ts b/src/modules/web-information/gate/data/services/gate-read.service.ts new file mode 100644 index 0000000..0dc74f2 --- /dev/null +++ b/src/modules/web-information/gate/data/services/gate-read.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { GateEntity } from '../../domain/entities/gate.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { GateModel } from '../models/gate.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 GateReadService extends BaseReadService { + constructor( + @InjectRepository(GateModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/web-information/gate/domain/entities/event/gate-change-status.event.ts b/src/modules/web-information/gate/domain/entities/event/gate-change-status.event.ts new file mode 100644 index 0000000..cba58f9 --- /dev/null +++ b/src/modules/web-information/gate/domain/entities/event/gate-change-status.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class GateChangeStatusEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/gate/domain/entities/event/gate-created.event.ts b/src/modules/web-information/gate/domain/entities/event/gate-created.event.ts new file mode 100644 index 0000000..808ce86 --- /dev/null +++ b/src/modules/web-information/gate/domain/entities/event/gate-created.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class GateCreatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/gate/domain/entities/event/gate-deleted.event.ts b/src/modules/web-information/gate/domain/entities/event/gate-deleted.event.ts new file mode 100644 index 0000000..a81314c --- /dev/null +++ b/src/modules/web-information/gate/domain/entities/event/gate-deleted.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class GateDeletedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/gate/domain/entities/event/gate-updated.event.ts b/src/modules/web-information/gate/domain/entities/event/gate-updated.event.ts new file mode 100644 index 0000000..2ccb9fd --- /dev/null +++ b/src/modules/web-information/gate/domain/entities/event/gate-updated.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class GateUpdatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/gate/domain/entities/filter-gate.entity.ts b/src/modules/web-information/gate/domain/entities/filter-gate.entity.ts new file mode 100644 index 0000000..36e1f08 --- /dev/null +++ b/src/modules/web-information/gate/domain/entities/filter-gate.entity.ts @@ -0,0 +1,8 @@ +import { BaseFilterEntity } from 'src/core/modules/domain/entities/base-filter.entity'; + +export interface FilterGateEntity extends BaseFilterEntity { + codes: string[]; + types: string[]; + item_names: string[]; + notes: string[]; +} diff --git a/src/modules/web-information/gate/domain/entities/gate.entity.ts b/src/modules/web-information/gate/domain/entities/gate.entity.ts new file mode 100644 index 0000000..5ef2f0a --- /dev/null +++ b/src/modules/web-information/gate/domain/entities/gate.entity.ts @@ -0,0 +1,10 @@ +import { BaseStatusEntity } from 'src/core/modules/domain/entities/base-status.entity'; +import { GateType } from '../../constants'; + +export interface GateEntity extends BaseStatusEntity { + type: GateType; + code: string; + note: string; + + item_id: string; +} diff --git a/src/modules/web-information/gate/domain/usecases/gate-data.orchestrator.ts b/src/modules/web-information/gate/domain/usecases/gate-data.orchestrator.ts new file mode 100644 index 0000000..1c6dd3e --- /dev/null +++ b/src/modules/web-information/gate/domain/usecases/gate-data.orchestrator.ts @@ -0,0 +1,106 @@ +import { Injectable } from '@nestjs/common'; +import { CreateGateManager } from './managers/create-gate.manager'; +import { GateDataService } from '../../data/services/gate-data.service'; +import { GateEntity } from '../entities/gate.entity'; +import { DeleteGateManager } from './managers/delete-gate.manager'; +import { UpdateGateManager } from './managers/update-gate.manager'; +import { BaseDataTransactionOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-data-transaction.orchestrator'; +import { ActiveGateManager } from './managers/active-gate.manager'; +import { InactiveGateManager } from './managers/inactive-gate.manager'; +import { ConfirmGateManager } from './managers/confirm-gate.manager'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchConfirmGateManager } from './managers/batch-confirm-gate.manager'; +import { BatchInactiveGateManager } from './managers/batch-inactive-gate.manager'; +import { BatchActiveGateManager } from './managers/batch-active-gate.manager'; +import { BatchDeleteGateManager } from './managers/batch-delete-gate.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@Injectable() +export class GateDataOrchestrator extends BaseDataTransactionOrchestrator { + constructor( + private createManager: CreateGateManager, + private updateManager: UpdateGateManager, + private deleteManager: DeleteGateManager, + private activeManager: ActiveGateManager, + private confirmManager: ConfirmGateManager, + private inactiveManager: InactiveGateManager, + private batchDeleteManager: BatchDeleteGateManager, + private batchActiveManager: BatchActiveGateManager, + private batchConfirmManager: BatchConfirmGateManager, + private batchInactiveManager: BatchInactiveGateManager, + private serviceData: GateDataService, + ) { + super(); + } + + async create(data): Promise { + this.createManager.setData(data); + this.createManager.setService(this.serviceData, TABLE_NAME.GATE); + await this.createManager.execute(); + return this.createManager.getResult(); + } + + async update(dataId, data): Promise { + this.updateManager.setData(dataId, data); + this.updateManager.setService(this.serviceData, TABLE_NAME.GATE); + await this.updateManager.execute(); + return this.updateManager.getResult(); + } + + async delete(dataId): Promise { + this.deleteManager.setData(dataId); + this.deleteManager.setService(this.serviceData, TABLE_NAME.GATE); + await this.deleteManager.execute(); + return this.deleteManager.getResult(); + } + + async batchDelete(dataIds: string[]): Promise { + this.batchDeleteManager.setData(dataIds); + this.batchDeleteManager.setService(this.serviceData, TABLE_NAME.GATE); + 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.GATE); + 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.GATE); + 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.GATE); + 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.GATE); + 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.GATE); + 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.GATE); + await this.batchInactiveManager.execute(); + return this.batchInactiveManager.getResult(); + } +} diff --git a/src/modules/web-information/gate/domain/usecases/gate-read.orchestrator.ts b/src/modules/web-information/gate/domain/usecases/gate-read.orchestrator.ts new file mode 100644 index 0000000..d7ed7f6 --- /dev/null +++ b/src/modules/web-information/gate/domain/usecases/gate-read.orchestrator.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@nestjs/common'; +import { IndexGateManager } from './managers/index-gate.manager'; +import { GateReadService } from '../../data/services/gate-read.service'; +import { GateEntity } from '../entities/gate.entity'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { BaseReadOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-read.orchestrator'; +import { DetailGateManager } from './managers/detail-gate.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@Injectable() +export class GateReadOrchestrator extends BaseReadOrchestrator { + constructor( + private indexManager: IndexGateManager, + private detailManager: DetailGateManager, + private serviceData: GateReadService, + ) { + super(); + } + + async index(params): Promise> { + this.indexManager.setFilterParam(params); + this.indexManager.setService(this.serviceData, TABLE_NAME.GATE); + await this.indexManager.execute(); + return this.indexManager.getResult(); + } + + async detail(dataId: string): Promise { + this.detailManager.setData(dataId); + this.detailManager.setService(this.serviceData, TABLE_NAME.GATE); + await this.detailManager.execute(); + return this.detailManager.getResult(); + } +} diff --git a/src/modules/web-information/gate/domain/usecases/managers/active-gate.manager.ts b/src/modules/web-information/gate/domain/usecases/managers/active-gate.manager.ts new file mode 100644 index 0000000..05a0cdc --- /dev/null +++ b/src/modules/web-information/gate/domain/usecases/managers/active-gate.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 { GateEntity } from '../../entities/gate.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { GateModel } from '../../../data/models/gate.model'; +import { GateChangeStatusEvent } from '../../entities/event/gate-change-status.event'; + +@Injectable() +export class ActiveGateManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data gate ${this.result.code}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return GateModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: GateChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/gate/domain/usecases/managers/batch-active-gate.manager.ts b/src/modules/web-information/gate/domain/usecases/managers/batch-active-gate.manager.ts new file mode 100644 index 0000000..1517173 --- /dev/null +++ b/src/modules/web-information/gate/domain/usecases/managers/batch-active-gate.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { GateEntity } from '../../entities/gate.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { GateModel } from '../../../data/models/gate.model'; +import { GateChangeStatusEvent } from '../../entities/event/gate-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchActiveGateManager extends BaseBatchUpdateStatusManager { + validateData(data: GateEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return GateModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: GateChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/gate/domain/usecases/managers/batch-confirm-gate.manager.ts b/src/modules/web-information/gate/domain/usecases/managers/batch-confirm-gate.manager.ts new file mode 100644 index 0000000..660e729 --- /dev/null +++ b/src/modules/web-information/gate/domain/usecases/managers/batch-confirm-gate.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { GateEntity } from '../../entities/gate.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { GateModel } from '../../../data/models/gate.model'; +import { GateChangeStatusEvent } from '../../entities/event/gate-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchConfirmGateManager extends BaseBatchUpdateStatusManager { + validateData(data: GateEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return GateModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: GateChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/gate/domain/usecases/managers/batch-delete-gate.manager.ts b/src/modules/web-information/gate/domain/usecases/managers/batch-delete-gate.manager.ts new file mode 100644 index 0000000..975f1fd --- /dev/null +++ b/src/modules/web-information/gate/domain/usecases/managers/batch-delete-gate.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchDeleteManager } from 'src/core/modules/domain/usecase/managers/base-batch-delete.manager'; +import { GateEntity } from '../../entities/gate.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { GateModel } from '../../../data/models/gate.model'; +import { GateDeletedEvent } from '../../entities/event/gate-deleted.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchDeleteGateManager extends BaseBatchDeleteManager { + async beforeProcess(): Promise { + return; + } + + async validateData(data: GateEntity): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return GateModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: GateDeletedEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/gate/domain/usecases/managers/batch-inactive-gate.manager.ts b/src/modules/web-information/gate/domain/usecases/managers/batch-inactive-gate.manager.ts new file mode 100644 index 0000000..6c3c055 --- /dev/null +++ b/src/modules/web-information/gate/domain/usecases/managers/batch-inactive-gate.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { GateEntity } from '../../entities/gate.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { GateModel } from '../../../data/models/gate.model'; +import { GateChangeStatusEvent } from '../../entities/event/gate-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchInactiveGateManager extends BaseBatchUpdateStatusManager { + validateData(data: GateEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return GateModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: GateChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/gate/domain/usecases/managers/confirm-gate.manager.ts b/src/modules/web-information/gate/domain/usecases/managers/confirm-gate.manager.ts new file mode 100644 index 0000000..5a409c5 --- /dev/null +++ b/src/modules/web-information/gate/domain/usecases/managers/confirm-gate.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 { GateEntity } from '../../entities/gate.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { GateModel } from '../../../data/models/gate.model'; +import { GateChangeStatusEvent } from '../../entities/event/gate-change-status.event'; + +@Injectable() +export class ConfirmGateManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data gate ${this.result.code}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return GateModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: GateChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/gate/domain/usecases/managers/create-gate.manager.ts b/src/modules/web-information/gate/domain/usecases/managers/create-gate.manager.ts new file mode 100644 index 0000000..d8ec3f4 --- /dev/null +++ b/src/modules/web-information/gate/domain/usecases/managers/create-gate.manager.ts @@ -0,0 +1,48 @@ +import { + HttpStatus, + Injectable, + UnprocessableEntityException, +} from '@nestjs/common'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { GateEntity } from '../../entities/gate.entity'; +import { GateModel } from '../../../data/models/gate.model'; +import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager'; +import { GateCreatedEvent } from '../../entities/event/gate-created.event'; +import { validateItemGate } from './helpers/validate-item-gate.helper'; + +@Injectable() +export class CreateGateManager extends BaseCreateManager { + async beforeProcess(): Promise { + await validateItemGate(this.dataService, this.data); + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: GateCreatedEvent, + data: this.data, + }, + ]; + } + + get entityTarget(): any { + return GateModel; + } +} diff --git a/src/modules/web-information/gate/domain/usecases/managers/delete-gate.manager.ts b/src/modules/web-information/gate/domain/usecases/managers/delete-gate.manager.ts new file mode 100644 index 0000000..f60323d --- /dev/null +++ b/src/modules/web-information/gate/domain/usecases/managers/delete-gate.manager.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDeleteManager } from 'src/core/modules/domain/usecase/managers/base-delete.manager'; +import { GateEntity } from '../../entities/gate.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { GateModel } from '../../../data/models/gate.model'; +import { GateDeletedEvent } from '../../entities/event/gate-deleted.event'; + +@Injectable() +export class DeleteGateManager 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 GateModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: GateDeletedEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/gate/domain/usecases/managers/detail-gate.manager.ts b/src/modules/web-information/gate/domain/usecases/managers/detail-gate.manager.ts new file mode 100644 index 0000000..39fdf54 --- /dev/null +++ b/src/modules/web-information/gate/domain/usecases/managers/detail-gate.manager.ts @@ -0,0 +1,59 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDetailManager } from 'src/core/modules/domain/usecase/managers/base-detail.manager'; +import { GateEntity } from '../../entities/gate.entity'; +import { RelationParam } from 'src/core/modules/domain/entities/base-filter.entity'; + +@Injectable() +export class DetailGateManager extends BaseDetailManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + // relation only join (for query purpose) + joinRelations: [], + + // relation join and select (relasi yang ingin ditampilkan), + selectRelations: ['item'], + + // relation yang hanya ingin dihitung (akan return number) + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.status`, + `${this.tableName}.creator_name`, + `${this.tableName}.created_at`, + `${this.tableName}.updated_at`, + `${this.tableName}.editor_name`, + `${this.tableName}.type`, + `${this.tableName}.code`, + `${this.tableName}.note`, + + 'item.id', + 'item.name', + 'item.status', + 'item.item_type', + 'item.base_price', + 'item.hpp', + ]; + } + + get setFindProperties(): any { + return { + id: this.dataId, + }; + } +} diff --git a/src/modules/web-information/gate/domain/usecases/managers/helpers/validate-item-gate.helper.ts b/src/modules/web-information/gate/domain/usecases/managers/helpers/validate-item-gate.helper.ts new file mode 100644 index 0000000..725d624 --- /dev/null +++ b/src/modules/web-information/gate/domain/usecases/managers/helpers/validate-item-gate.helper.ts @@ -0,0 +1,38 @@ +import { HttpStatus, UnprocessableEntityException } from '@nestjs/common'; +import { EMPTY_UUID } from 'src/core/strings/constants/base.constants'; +import { In, Not } from 'typeorm'; + +export async function validateItemGate(dataService, data, id?) { + // validate same code + const existCode = await dataService.getOneByOptions({ + where: { + code: data.code, + id: Not(In([id ?? EMPTY_UUID])), + }, + }); + + if (existCode) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! Gate with code ${data.code} already exist`, + error: 'Unprocessable Entity', + }); + } + + // validate type item + const existType = await dataService.getOneByOptions({ + where: { + item_id: data.item.id, + type: data.type, + id: Not(In([id ?? EMPTY_UUID])), + }, + }); + + if (existType) { + throw new UnprocessableEntityException({ + statusCode: HttpStatus.UNPROCESSABLE_ENTITY, + message: `Failed! Gate type ${data.type} with item ${data.item.name} already exist`, + error: 'Unprocessable Entity', + }); + } +} diff --git a/src/modules/web-information/gate/domain/usecases/managers/inactive-gate.manager.ts b/src/modules/web-information/gate/domain/usecases/managers/inactive-gate.manager.ts new file mode 100644 index 0000000..94c08ab --- /dev/null +++ b/src/modules/web-information/gate/domain/usecases/managers/inactive-gate.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 { GateEntity } from '../../entities/gate.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { GateModel } from '../../../data/models/gate.model'; +import { GateChangeStatusEvent } from '../../entities/event/gate-change-status.event'; + +@Injectable() +export class InactiveGateManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success inactive data gate ${this.result.code}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return GateModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: GateChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/gate/domain/usecases/managers/index-gate.manager.ts b/src/modules/web-information/gate/domain/usecases/managers/index-gate.manager.ts new file mode 100644 index 0000000..0298552 --- /dev/null +++ b/src/modules/web-information/gate/domain/usecases/managers/index-gate.manager.ts @@ -0,0 +1,84 @@ +import { Injectable } from '@nestjs/common'; +import { BaseIndexManager } from 'src/core/modules/domain/usecase/managers/base-index.manager'; +import { GateEntity } from '../../entities/gate.entity'; +import { SelectQueryBuilder } from 'typeorm'; +import { + Param, + RelationParam, +} from 'src/core/modules/domain/entities/base-filter.entity'; + +@Injectable() +export class IndexGateManager extends BaseIndexManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + // relation only join (for query purpose) + joinRelations: [], + + // relation join and select (relasi yang ingin ditampilkan), + selectRelations: ['item'], + + // relation yang hanya ingin dihitung (akan return number) + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.status`, + `${this.tableName}.creator_name`, + `${this.tableName}.created_at`, + `${this.tableName}.updated_at`, + `${this.tableName}.editor_name`, + `${this.tableName}.type`, + `${this.tableName}.code`, + `${this.tableName}.note`, + + 'item.id', + 'item.name', + 'item.status', + 'item.item_type', + 'item.base_price', + 'item.hpp', + ]; + } + + get specificFilter(): Param[] { + return [ + { + cols: `${this.tableName}.code`, + data: this.filterParam.codes, + }, + { + cols: `${this.tableName}.type`, + data: this.filterParam.types, + }, + { + cols: `${this.tableName}.note`, + data: this.filterParam.notes, + }, + { + cols: `item.name`, + data: this.filterParam.item_names, + }, + ]; + } + + setQueryFilter( + queryBuilder: SelectQueryBuilder, + ): SelectQueryBuilder { + return queryBuilder; + } +} diff --git a/src/modules/web-information/gate/domain/usecases/managers/update-gate.manager.ts b/src/modules/web-information/gate/domain/usecases/managers/update-gate.manager.ts new file mode 100644 index 0000000..1018675 --- /dev/null +++ b/src/modules/web-information/gate/domain/usecases/managers/update-gate.manager.ts @@ -0,0 +1,48 @@ +import { Injectable } from '@nestjs/common'; +import { BaseUpdateManager } from 'src/core/modules/domain/usecase/managers/base-update.manager'; +import { GateEntity } from '../../entities/gate.entity'; +import { GateModel } from '../../../data/models/gate.model'; +import { GateUpdatedEvent } from '../../entities/event/gate-updated.event'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { validateItemGate } from './helpers/validate-item-gate.helper'; + +@Injectable() +export class UpdateGateManager extends BaseUpdateManager { + async validateProcess(): Promise { + await validateItemGate(this.dataService, this.data, this.dataId); + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get entityTarget(): any { + return GateModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: GateUpdatedEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/gate/gate.module.ts b/src/modules/web-information/gate/gate.module.ts new file mode 100644 index 0000000..92298cb --- /dev/null +++ b/src/modules/web-information/gate/gate.module.ts @@ -0,0 +1,54 @@ +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 { GateDataService } from './data/services/gate-data.service'; +import { GateReadService } from './data/services/gate-read.service'; +import { GateReadController } from './infrastructure/gate-read.controller'; +import { GateReadOrchestrator } from './domain/usecases/gate-read.orchestrator'; +import { GateDataController } from './infrastructure/gate-data.controller'; +import { GateDataOrchestrator } from './domain/usecases/gate-data.orchestrator'; +import { CreateGateManager } from './domain/usecases/managers/create-gate.manager'; +import { CqrsModule } from '@nestjs/cqrs'; +import { IndexGateManager } from './domain/usecases/managers/index-gate.manager'; +import { DeleteGateManager } from './domain/usecases/managers/delete-gate.manager'; +import { UpdateGateManager } from './domain/usecases/managers/update-gate.manager'; +import { ActiveGateManager } from './domain/usecases/managers/active-gate.manager'; +import { ConfirmGateManager } from './domain/usecases/managers/confirm-gate.manager'; +import { InactiveGateManager } from './domain/usecases/managers/inactive-gate.manager'; +import { DetailGateManager } from './domain/usecases/managers/detail-gate.manager'; +import { BatchDeleteGateManager } from './domain/usecases/managers/batch-delete-gate.manager'; +import { BatchActiveGateManager } from './domain/usecases/managers/batch-active-gate.manager'; +import { BatchConfirmGateManager } from './domain/usecases/managers/batch-confirm-gate.manager'; +import { BatchInactiveGateManager } from './domain/usecases/managers/batch-inactive-gate.manager'; +import { GateModel } from './data/models/gate.model'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature([GateModel], CONNECTION_NAME.DEFAULT), + CqrsModule, + ], + controllers: [GateDataController, GateReadController], + providers: [ + IndexGateManager, + DetailGateManager, + CreateGateManager, + DeleteGateManager, + UpdateGateManager, + ActiveGateManager, + ConfirmGateManager, + InactiveGateManager, + BatchDeleteGateManager, + BatchActiveGateManager, + BatchConfirmGateManager, + BatchInactiveGateManager, + + GateDataService, + GateReadService, + + GateDataOrchestrator, + GateReadOrchestrator, + ], +}) +export class GateModule {} diff --git a/src/modules/web-information/gate/index.ts b/src/modules/web-information/gate/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/web-information/gate/infrastructure/dto/filter-gate.dto.ts b/src/modules/web-information/gate/infrastructure/dto/filter-gate.dto.ts new file mode 100644 index 0000000..d3da747 --- /dev/null +++ b/src/modules/web-information/gate/infrastructure/dto/filter-gate.dto.ts @@ -0,0 +1,30 @@ +import { BaseFilterDto } from 'src/core/modules/infrastructure/dto/base-filter.dto'; +import { FilterGateEntity } from '../../domain/entities/filter-gate.entity'; +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; + +export class FilterGateDto extends BaseFilterDto implements FilterGateEntity { + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + codes: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + types: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + item_names: string[]; + + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + notes: string[]; +} diff --git a/src/modules/web-information/gate/infrastructure/dto/gate.dto.ts b/src/modules/web-information/gate/infrastructure/dto/gate.dto.ts new file mode 100644 index 0000000..37167d5 --- /dev/null +++ b/src/modules/web-information/gate/infrastructure/dto/gate.dto.ts @@ -0,0 +1,45 @@ +import { BaseStatusDto } from 'src/core/modules/infrastructure/dto/base-status.dto'; +import { GateEntity } from '../../domain/entities/gate.entity'; +import { GateType } from '../../constants'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsObject, IsString } from 'class-validator'; +import { Exclude } from 'class-transformer'; +import { ItemEntity } from 'src/modules/item-related/item/domain/entities/item.entity'; + +export class GateDto extends BaseStatusDto implements GateEntity { + @ApiProperty({ + type: String, + required: true, + example: GateType.GATE_IN, + }) + @IsString() + type: GateType; + + @ApiProperty({ + type: String, + required: true, + example: '41245', + }) + code: string; + + @ApiProperty({ + type: String, + required: false, + example: '41245', + }) + note: string; + + @ApiProperty({ + type: Object, + required: true, + example: { + id: 'uuid', + name: 'whana', + }, + }) + @IsObject() + item: ItemEntity; + + @Exclude() + item_id: string; +} diff --git a/src/modules/web-information/gate/infrastructure/gate-data.controller.ts b/src/modules/web-information/gate/infrastructure/gate-data.controller.ts new file mode 100644 index 0000000..96074d9 --- /dev/null +++ b/src/modules/web-information/gate/infrastructure/gate-data.controller.ts @@ -0,0 +1,78 @@ +import { + Body, + Controller, + Delete, + Param, + Patch, + Post, + Put, +} from '@nestjs/common'; +import { GateDataOrchestrator } from '../domain/usecases/gate-data.orchestrator'; +import { GateDto } from './dto/gate.dto'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { GateEntity } from '../domain/entities/gate.entity'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto'; +import { Public } from 'src/core/guards'; + +@ApiTags(`${MODULE_NAME.GATE.split('-').join(' ')} - data`) +@Controller(`v1/${MODULE_NAME.GATE}`) +@Public(false) +@ApiBearerAuth('JWT') +export class GateDataController { + constructor(private orchestrator: GateDataOrchestrator) {} + + @Post() + async create(@Body() data: GateDto): 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: GateDto, + ): Promise { + return await this.orchestrator.update(dataId, data); + } + + @Delete(':id') + async delete(@Param('id') dataId: string): Promise { + return await this.orchestrator.delete(dataId); + } +} diff --git a/src/modules/web-information/gate/infrastructure/gate-read.controller.ts b/src/modules/web-information/gate/infrastructure/gate-read.controller.ts new file mode 100644 index 0000000..b6d213a --- /dev/null +++ b/src/modules/web-information/gate/infrastructure/gate-read.controller.ts @@ -0,0 +1,30 @@ +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { FilterGateDto } from './dto/filter-gate.dto'; +import { Pagination } from 'src/core/response'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { GateEntity } from '../domain/entities/gate.entity'; +import { GateReadOrchestrator } from '../domain/usecases/gate-read.orchestrator'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { Public } from 'src/core/guards'; + +@ApiTags(`${MODULE_NAME.GATE.split('-').join(' ')} - read`) +@Controller(`v1/${MODULE_NAME.GATE}`) +@Public(false) +@ApiBearerAuth('JWT') +export class GateReadController { + constructor(private orchestrator: GateReadOrchestrator) {} + + @Get() + @Pagination() + async index( + @Query() params: FilterGateDto, + ): Promise> { + return await this.orchestrator.index(params); + } + + @Get(':id') + async detail(@Param('id') id: string): Promise { + return await this.orchestrator.detail(id); + } +} diff --git a/src/modules/web-information/news/constants.ts b/src/modules/web-information/news/constants.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/web-information/news/data/models/news.model.ts b/src/modules/web-information/news/data/models/news.model.ts new file mode 100644 index 0000000..b74b247 --- /dev/null +++ b/src/modules/web-information/news/data/models/news.model.ts @@ -0,0 +1,22 @@ +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { NewsEntity } from '../../domain/entities/news.entity'; +import { Column, Entity } from 'typeorm'; +import { BaseStatusModel } from 'src/core/modules/data/model/base-status.model'; + +@Entity(TABLE_NAME.NEWS) +export class NewsModel + extends BaseStatusModel + implements NewsEntity +{ + @Column('varchar', { name: 'image_url', nullable: true }) + image_url: string; + + @Column('varchar', { name: 'title', nullable: true }) + title: string; + + @Column('varchar', { name: 'teaser', nullable: true }) + teaser: string; + + @Column('varchar', { name: 'description', nullable: true }) + description: string; +} diff --git a/src/modules/web-information/news/data/services/news-data.service.ts b/src/modules/web-information/news/data/services/news-data.service.ts new file mode 100644 index 0000000..6e2846d --- /dev/null +++ b/src/modules/web-information/news/data/services/news-data.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; +import { NewsEntity } from '../../domain/entities/news.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { NewsModel } from '../models/news.model'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { Repository } from 'typeorm'; + +@Injectable() +export class NewsDataService extends BaseDataService { + constructor( + @InjectRepository(NewsModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/web-information/news/data/services/news-read.service.ts b/src/modules/web-information/news/data/services/news-read.service.ts new file mode 100644 index 0000000..e9ed46b --- /dev/null +++ b/src/modules/web-information/news/data/services/news-read.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { NewsEntity } from '../../domain/entities/news.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { NewsModel } from '../models/news.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 NewsReadService extends BaseReadService { + constructor( + @InjectRepository(NewsModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/web-information/news/domain/entities/event/news-change-status.event.ts b/src/modules/web-information/news/domain/entities/event/news-change-status.event.ts new file mode 100644 index 0000000..e45723f --- /dev/null +++ b/src/modules/web-information/news/domain/entities/event/news-change-status.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class NewsChangeStatusEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/news/domain/entities/event/news-created.event.ts b/src/modules/web-information/news/domain/entities/event/news-created.event.ts new file mode 100644 index 0000000..8e0f2ec --- /dev/null +++ b/src/modules/web-information/news/domain/entities/event/news-created.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class NewsCreatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/news/domain/entities/event/news-deleted.event.ts b/src/modules/web-information/news/domain/entities/event/news-deleted.event.ts new file mode 100644 index 0000000..ee1d268 --- /dev/null +++ b/src/modules/web-information/news/domain/entities/event/news-deleted.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class NewsDeletedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/news/domain/entities/event/news-updated.event.ts b/src/modules/web-information/news/domain/entities/event/news-updated.event.ts new file mode 100644 index 0000000..6d30473 --- /dev/null +++ b/src/modules/web-information/news/domain/entities/event/news-updated.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class NewsUpdatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/news/domain/entities/filter-news.entity.ts b/src/modules/web-information/news/domain/entities/filter-news.entity.ts new file mode 100644 index 0000000..20f1802 --- /dev/null +++ b/src/modules/web-information/news/domain/entities/filter-news.entity.ts @@ -0,0 +1,5 @@ +import { BaseFilterEntity } from 'src/core/modules/domain/entities/base-filter.entity'; + +export interface FilterNewsEntity extends BaseFilterEntity { + titles: string[]; +} diff --git a/src/modules/web-information/news/domain/entities/news.entity.ts b/src/modules/web-information/news/domain/entities/news.entity.ts new file mode 100644 index 0000000..f71ec1a --- /dev/null +++ b/src/modules/web-information/news/domain/entities/news.entity.ts @@ -0,0 +1,8 @@ +import { BaseStatusEntity } from 'src/core/modules/domain/entities/base-status.entity'; + +export interface NewsEntity extends BaseStatusEntity { + image_url: string; + title: string; + teaser: string; + description: string; +} diff --git a/src/modules/web-information/news/domain/usecases/managers/active-news.manager.ts b/src/modules/web-information/news/domain/usecases/managers/active-news.manager.ts new file mode 100644 index 0000000..934230c --- /dev/null +++ b/src/modules/web-information/news/domain/usecases/managers/active-news.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 { NewsEntity } from '../../entities/news.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { NewsModel } from '../../../data/models/news.model'; +import { NewsChangeStatusEvent } from '../../entities/event/news-change-status.event'; + +@Injectable() +export class ActiveNewsManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.title}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return NewsModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: NewsChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/news/domain/usecases/managers/batch-active-news.manager.ts b/src/modules/web-information/news/domain/usecases/managers/batch-active-news.manager.ts new file mode 100644 index 0000000..3bfb58d --- /dev/null +++ b/src/modules/web-information/news/domain/usecases/managers/batch-active-news.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { NewsEntity } from '../../entities/news.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { NewsModel } from '../../../data/models/news.model'; +import { NewsChangeStatusEvent } from '../../entities/event/news-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchActiveNewsManager extends BaseBatchUpdateStatusManager { + validateData(data: NewsEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return NewsModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: NewsChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/news/domain/usecases/managers/batch-confirm-news.manager.ts b/src/modules/web-information/news/domain/usecases/managers/batch-confirm-news.manager.ts new file mode 100644 index 0000000..171e091 --- /dev/null +++ b/src/modules/web-information/news/domain/usecases/managers/batch-confirm-news.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { NewsEntity } from '../../entities/news.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { NewsModel } from '../../../data/models/news.model'; +import { NewsChangeStatusEvent } from '../../entities/event/news-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchConfirmNewsManager extends BaseBatchUpdateStatusManager { + validateData(data: NewsEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return NewsModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: NewsChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/news/domain/usecases/managers/batch-delete-news.manager.ts b/src/modules/web-information/news/domain/usecases/managers/batch-delete-news.manager.ts new file mode 100644 index 0000000..9a88df9 --- /dev/null +++ b/src/modules/web-information/news/domain/usecases/managers/batch-delete-news.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchDeleteManager } from 'src/core/modules/domain/usecase/managers/base-batch-delete.manager'; +import { NewsEntity } from '../../entities/news.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { NewsModel } from '../../../data/models/news.model'; +import { NewsDeletedEvent } from '../../entities/event/news-deleted.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchDeleteNewsManager extends BaseBatchDeleteManager { + async beforeProcess(): Promise { + return; + } + + async validateData(data: NewsEntity): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return NewsModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: NewsDeletedEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/news/domain/usecases/managers/batch-inactive-news.manager.ts b/src/modules/web-information/news/domain/usecases/managers/batch-inactive-news.manager.ts new file mode 100644 index 0000000..e683418 --- /dev/null +++ b/src/modules/web-information/news/domain/usecases/managers/batch-inactive-news.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { NewsEntity } from '../../entities/news.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { NewsModel } from '../../../data/models/news.model'; +import { NewsChangeStatusEvent } from '../../entities/event/news-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchInactiveNewsManager extends BaseBatchUpdateStatusManager { + validateData(data: NewsEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return NewsModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: NewsChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/news/domain/usecases/managers/confirm-news.manager.ts b/src/modules/web-information/news/domain/usecases/managers/confirm-news.manager.ts new file mode 100644 index 0000000..50a20c8 --- /dev/null +++ b/src/modules/web-information/news/domain/usecases/managers/confirm-news.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 { NewsEntity } from '../../entities/news.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { NewsModel } from '../../../data/models/news.model'; +import { NewsChangeStatusEvent } from '../../entities/event/news-change-status.event'; + +@Injectable() +export class ConfirmNewsManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.title}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return NewsModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: NewsChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/news/domain/usecases/managers/create-news.manager.ts b/src/modules/web-information/news/domain/usecases/managers/create-news.manager.ts new file mode 100644 index 0000000..b806003 --- /dev/null +++ b/src/modules/web-information/news/domain/usecases/managers/create-news.manager.ts @@ -0,0 +1,42 @@ +import { Injectable } from '@nestjs/common'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { NewsEntity } from '../../entities/news.entity'; +import { NewsModel } from '../../../data/models/news.model'; +import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager'; +import { NewsCreatedEvent } from '../../entities/event/news-created.event'; + +@Injectable() +export class CreateNewsManager extends BaseCreateManager { + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: NewsCreatedEvent, + data: this.data, + }, + ]; + } + + get entityTarget(): any { + return NewsModel; + } +} diff --git a/src/modules/web-information/news/domain/usecases/managers/delete-news.manager.ts b/src/modules/web-information/news/domain/usecases/managers/delete-news.manager.ts new file mode 100644 index 0000000..f8890b1 --- /dev/null +++ b/src/modules/web-information/news/domain/usecases/managers/delete-news.manager.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDeleteManager } from 'src/core/modules/domain/usecase/managers/base-delete.manager'; +import { NewsEntity } from '../../entities/news.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { NewsModel } from '../../../data/models/news.model'; +import { NewsDeletedEvent } from '../../entities/event/news-deleted.event'; + +@Injectable() +export class DeleteNewsManager 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 NewsModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: NewsDeletedEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/news/domain/usecases/managers/detail-news.manager.ts b/src/modules/web-information/news/domain/usecases/managers/detail-news.manager.ts new file mode 100644 index 0000000..092c785 --- /dev/null +++ b/src/modules/web-information/news/domain/usecases/managers/detail-news.manager.ts @@ -0,0 +1,54 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDetailManager } from 'src/core/modules/domain/usecase/managers/base-detail.manager'; +import { NewsEntity } from '../../entities/news.entity'; +import { RelationParam } from 'src/core/modules/domain/entities/base-filter.entity'; + +@Injectable() +export class DetailNewsManager extends BaseDetailManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + // relation only join (for query purpose) + joinRelations: [], + + // relation join and select (relasi yang ingin ditampilkan), + selectRelations: [], + + // relation yang hanya ingin dihitung (akan return number) + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.created_at`, + `${this.tableName}.creator_name`, + `${this.tableName}.updated_at`, + `${this.tableName}.editor_name`, + + `${this.tableName}.status`, + `${this.tableName}.image_url`, + `${this.tableName}.title`, + `${this.tableName}.teaser`, + `${this.tableName}.description`, + ]; + } + + get setFindProperties(): any { + return { + id: this.dataId, + }; + } +} diff --git a/src/modules/web-information/news/domain/usecases/managers/inactive-news.manager.ts b/src/modules/web-information/news/domain/usecases/managers/inactive-news.manager.ts new file mode 100644 index 0000000..fb1ee1a --- /dev/null +++ b/src/modules/web-information/news/domain/usecases/managers/inactive-news.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 { NewsEntity } from '../../entities/news.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { NewsModel } from '../../../data/models/news.model'; +import { NewsChangeStatusEvent } from '../../entities/event/news-change-status.event'; + +@Injectable() +export class InactiveNewsManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success inactive data ${this.result.title}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return NewsModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: NewsChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/news/domain/usecases/managers/index-news.manager.ts b/src/modules/web-information/news/domain/usecases/managers/index-news.manager.ts new file mode 100644 index 0000000..d779b5f --- /dev/null +++ b/src/modules/web-information/news/domain/usecases/managers/index-news.manager.ts @@ -0,0 +1,67 @@ +import { Injectable } from '@nestjs/common'; +import { BaseIndexManager } from 'src/core/modules/domain/usecase/managers/base-index.manager'; +import { NewsEntity } from '../../entities/news.entity'; +import { SelectQueryBuilder } from 'typeorm'; +import { + Param, + RelationParam, +} from 'src/core/modules/domain/entities/base-filter.entity'; + +@Injectable() +export class IndexNewsManager extends BaseIndexManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + // relation only join (for query purpose) + joinRelations: [], + + // relation join and select (relasi yang ingin ditampilkan), + selectRelations: [], + + // relation yang hanya ingin dihitung (akan return number) + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.created_at`, + `${this.tableName}.creator_name`, + `${this.tableName}.updated_at`, + `${this.tableName}.editor_name`, + + `${this.tableName}.status`, + `${this.tableName}.image_url`, + `${this.tableName}.title`, + `${this.tableName}.teaser`, + `${this.tableName}.description`, + ]; + } + + get specificFilter(): Param[] { + return [ + { + cols: `${this.tableName}.title`, + data: this.filterParam.titles, + }, + ]; + } + + setQueryFilter( + queryBuilder: SelectQueryBuilder, + ): SelectQueryBuilder { + return queryBuilder; + } +} diff --git a/src/modules/web-information/news/domain/usecases/managers/update-news.manager.ts b/src/modules/web-information/news/domain/usecases/managers/update-news.manager.ts new file mode 100644 index 0000000..695d0d5 --- /dev/null +++ b/src/modules/web-information/news/domain/usecases/managers/update-news.manager.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@nestjs/common'; +import { BaseUpdateManager } from 'src/core/modules/domain/usecase/managers/base-update.manager'; +import { NewsEntity } from '../../entities/news.entity'; +import { NewsModel } from '../../../data/models/news.model'; +import { NewsUpdatedEvent } from '../../entities/event/news-updated.event'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; + +@Injectable() +export class UpdateNewsManager extends BaseUpdateManager { + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get entityTarget(): any { + return NewsModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: NewsUpdatedEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/news/domain/usecases/news-data.orchestrator.ts b/src/modules/web-information/news/domain/usecases/news-data.orchestrator.ts new file mode 100644 index 0000000..e428a13 --- /dev/null +++ b/src/modules/web-information/news/domain/usecases/news-data.orchestrator.ts @@ -0,0 +1,106 @@ +import { Injectable } from '@nestjs/common'; +import { CreateNewsManager } from './managers/create-news.manager'; +import { NewsDataService } from '../../data/services/news-data.service'; +import { NewsEntity } from '../entities/news.entity'; +import { DeleteNewsManager } from './managers/delete-news.manager'; +import { UpdateNewsManager } from './managers/update-news.manager'; +import { BaseDataTransactionOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-data-transaction.orchestrator'; +import { ActiveNewsManager } from './managers/active-news.manager'; +import { InactiveNewsManager } from './managers/inactive-news.manager'; +import { ConfirmNewsManager } from './managers/confirm-news.manager'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchConfirmNewsManager } from './managers/batch-confirm-news.manager'; +import { BatchInactiveNewsManager } from './managers/batch-inactive-news.manager'; +import { BatchActiveNewsManager } from './managers/batch-active-news.manager'; +import { BatchDeleteNewsManager } from './managers/batch-delete-news.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@Injectable() +export class NewsDataOrchestrator extends BaseDataTransactionOrchestrator { + constructor( + private createManager: CreateNewsManager, + private updateManager: UpdateNewsManager, + private deleteManager: DeleteNewsManager, + private activeManager: ActiveNewsManager, + private confirmManager: ConfirmNewsManager, + private inactiveManager: InactiveNewsManager, + private batchDeleteManager: BatchDeleteNewsManager, + private batchActiveManager: BatchActiveNewsManager, + private batchConfirmManager: BatchConfirmNewsManager, + private batchInactiveManager: BatchInactiveNewsManager, + private serviceData: NewsDataService, + ) { + super(); + } + + async create(data): Promise { + this.createManager.setData(data); + this.createManager.setService(this.serviceData, TABLE_NAME.NEWS); + await this.createManager.execute(); + return this.createManager.getResult(); + } + + async update(dataId, data): Promise { + this.updateManager.setData(dataId, data); + this.updateManager.setService(this.serviceData, TABLE_NAME.NEWS); + await this.updateManager.execute(); + return this.updateManager.getResult(); + } + + async delete(dataId): Promise { + this.deleteManager.setData(dataId); + this.deleteManager.setService(this.serviceData, TABLE_NAME.NEWS); + await this.deleteManager.execute(); + return this.deleteManager.getResult(); + } + + async batchDelete(dataIds: string[]): Promise { + this.batchDeleteManager.setData(dataIds); + this.batchDeleteManager.setService(this.serviceData, TABLE_NAME.NEWS); + 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.NEWS); + 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.NEWS); + 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.NEWS); + 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.NEWS); + 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.NEWS); + 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.NEWS); + await this.batchInactiveManager.execute(); + return this.batchInactiveManager.getResult(); + } +} diff --git a/src/modules/web-information/news/domain/usecases/news-read.orchestrator.ts b/src/modules/web-information/news/domain/usecases/news-read.orchestrator.ts new file mode 100644 index 0000000..15df4c5 --- /dev/null +++ b/src/modules/web-information/news/domain/usecases/news-read.orchestrator.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@nestjs/common'; +import { IndexNewsManager } from './managers/index-news.manager'; +import { NewsReadService } from '../../data/services/news-read.service'; +import { NewsEntity } from '../entities/news.entity'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { BaseReadOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-read.orchestrator'; +import { DetailNewsManager } from './managers/detail-news.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@Injectable() +export class NewsReadOrchestrator extends BaseReadOrchestrator { + constructor( + private indexManager: IndexNewsManager, + private detailManager: DetailNewsManager, + private serviceData: NewsReadService, + ) { + super(); + } + + async index(params): Promise> { + this.indexManager.setFilterParam(params); + this.indexManager.setService(this.serviceData, TABLE_NAME.NEWS); + await this.indexManager.execute(); + return this.indexManager.getResult(); + } + + async detail(dataId: string): Promise { + this.detailManager.setData(dataId); + this.detailManager.setService(this.serviceData, TABLE_NAME.NEWS); + await this.detailManager.execute(); + return this.detailManager.getResult(); + } +} diff --git a/src/modules/web-information/news/index.ts b/src/modules/web-information/news/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/web-information/news/infrastructure/dto/filter-news.dto.ts b/src/modules/web-information/news/infrastructure/dto/filter-news.dto.ts new file mode 100644 index 0000000..294c305 --- /dev/null +++ b/src/modules/web-information/news/infrastructure/dto/filter-news.dto.ts @@ -0,0 +1,12 @@ +import { BaseFilterDto } from 'src/core/modules/infrastructure/dto/base-filter.dto'; +import { FilterNewsEntity } from '../../domain/entities/filter-news.entity'; +import { ApiProperty } from '@nestjs/swagger'; +import { Transform } from 'class-transformer'; + +export class FilterNewsDto extends BaseFilterDto implements FilterNewsEntity { + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + titles: string[]; +} diff --git a/src/modules/web-information/news/infrastructure/dto/news.dto.ts b/src/modules/web-information/news/infrastructure/dto/news.dto.ts new file mode 100644 index 0000000..20d5c70 --- /dev/null +++ b/src/modules/web-information/news/infrastructure/dto/news.dto.ts @@ -0,0 +1,33 @@ +import { BaseStatusDto } from 'src/core/modules/infrastructure/dto/base-status.dto'; +import { NewsEntity } from '../../domain/entities/news.entity'; +import { ApiProperty } from '@nestjs/swagger'; + +export class NewsDto extends BaseStatusDto implements NewsEntity { + @ApiProperty({ + type: String, + required: false, + example: 'https://...', + }) + image_url: string; + + @ApiProperty({ + type: String, + required: true, + example: 'The Flash Speed Force', + }) + title: string; + + @ApiProperty({ + type: String, + required: false, + example: 'Get ready to take on the first ride-within-a-ride experience', + }) + teaser: string; + + @ApiProperty({ + type: String, + required: false, + example: 'description', + }) + description: string; +} diff --git a/src/modules/web-information/news/infrastructure/news-data.controller.ts b/src/modules/web-information/news/infrastructure/news-data.controller.ts new file mode 100644 index 0000000..61b8b1e --- /dev/null +++ b/src/modules/web-information/news/infrastructure/news-data.controller.ts @@ -0,0 +1,78 @@ +import { + Body, + Controller, + Delete, + Param, + Patch, + Post, + Put, +} from '@nestjs/common'; +import { NewsDataOrchestrator } from '../domain/usecases/news-data.orchestrator'; +import { NewsDto } from './dto/news.dto'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { NewsEntity } from '../domain/entities/news.entity'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto'; +import { Public } from 'src/core/guards'; + +@ApiTags(`${MODULE_NAME.NEWS.split('-').join(' ')} - data`) +@Controller(`v1/${MODULE_NAME.NEWS}`) +@Public(false) +@ApiBearerAuth('JWT') +export class NewsDataController { + constructor(private orchestrator: NewsDataOrchestrator) {} + + @Post() + async create(@Body() data: NewsDto): 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: NewsDto, + ): Promise { + return await this.orchestrator.update(dataId, data); + } + + @Delete(':id') + async delete(@Param('id') dataId: string): Promise { + return await this.orchestrator.delete(dataId); + } +} diff --git a/src/modules/web-information/news/infrastructure/news-read.controller.ts b/src/modules/web-information/news/infrastructure/news-read.controller.ts new file mode 100644 index 0000000..1e31953 --- /dev/null +++ b/src/modules/web-information/news/infrastructure/news-read.controller.ts @@ -0,0 +1,30 @@ +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { FilterNewsDto } from './dto/filter-news.dto'; +import { Pagination } from 'src/core/response'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { NewsEntity } from '../domain/entities/news.entity'; +import { NewsReadOrchestrator } from '../domain/usecases/news-read.orchestrator'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { Public } from 'src/core/guards'; + +@ApiTags(`${MODULE_NAME.NEWS.split('-').join(' ')} - read`) +@Controller(`v1/${MODULE_NAME.NEWS}`) +@Public(false) +@ApiBearerAuth('JWT') +export class NewsReadController { + constructor(private orchestrator: NewsReadOrchestrator) {} + + @Get() + @Pagination() + async index( + @Query() params: FilterNewsDto, + ): Promise> { + return await this.orchestrator.index(params); + } + + @Get(':id') + async detail(@Param('id') id: string): Promise { + return await this.orchestrator.detail(id); + } +} diff --git a/src/modules/web-information/news/news.module.ts b/src/modules/web-information/news/news.module.ts new file mode 100644 index 0000000..0e9e522 --- /dev/null +++ b/src/modules/web-information/news/news.module.ts @@ -0,0 +1,54 @@ +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 { NewsDataService } from './data/services/news-data.service'; +import { NewsReadService } from './data/services/news-read.service'; +import { NewsReadController } from './infrastructure/news-read.controller'; +import { NewsReadOrchestrator } from './domain/usecases/news-read.orchestrator'; +import { NewsDataController } from './infrastructure/news-data.controller'; +import { NewsDataOrchestrator } from './domain/usecases/news-data.orchestrator'; +import { CreateNewsManager } from './domain/usecases/managers/create-news.manager'; +import { CqrsModule } from '@nestjs/cqrs'; +import { IndexNewsManager } from './domain/usecases/managers/index-news.manager'; +import { DeleteNewsManager } from './domain/usecases/managers/delete-news.manager'; +import { UpdateNewsManager } from './domain/usecases/managers/update-news.manager'; +import { ActiveNewsManager } from './domain/usecases/managers/active-news.manager'; +import { ConfirmNewsManager } from './domain/usecases/managers/confirm-news.manager'; +import { InactiveNewsManager } from './domain/usecases/managers/inactive-news.manager'; +import { DetailNewsManager } from './domain/usecases/managers/detail-news.manager'; +import { BatchDeleteNewsManager } from './domain/usecases/managers/batch-delete-news.manager'; +import { BatchActiveNewsManager } from './domain/usecases/managers/batch-active-news.manager'; +import { BatchConfirmNewsManager } from './domain/usecases/managers/batch-confirm-news.manager'; +import { BatchInactiveNewsManager } from './domain/usecases/managers/batch-inactive-news.manager'; +import { NewsModel } from './data/models/news.model'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature([NewsModel], CONNECTION_NAME.DEFAULT), + CqrsModule, + ], + controllers: [NewsDataController, NewsReadController], + providers: [ + IndexNewsManager, + DetailNewsManager, + CreateNewsManager, + DeleteNewsManager, + UpdateNewsManager, + ActiveNewsManager, + ConfirmNewsManager, + InactiveNewsManager, + BatchDeleteNewsManager, + BatchActiveNewsManager, + BatchConfirmNewsManager, + BatchInactiveNewsManager, + + NewsDataService, + NewsReadService, + + NewsDataOrchestrator, + NewsReadOrchestrator, + ], +}) +export class NewsModule {} diff --git a/src/modules/web-information/term-condition/constants.ts b/src/modules/web-information/term-condition/constants.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/web-information/term-condition/data/models/term-condition.model.ts b/src/modules/web-information/term-condition/data/models/term-condition.model.ts new file mode 100644 index 0000000..529570b --- /dev/null +++ b/src/modules/web-information/term-condition/data/models/term-condition.model.ts @@ -0,0 +1,19 @@ +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { TermConditionEntity } from '../../domain/entities/term-condition.entity'; +import { Column, Entity } from 'typeorm'; +import { BaseStatusModel } from 'src/core/modules/data/model/base-status.model'; + +@Entity(TABLE_NAME.TERM_CONDITION) +export class TermConditionModel + extends BaseStatusModel + implements TermConditionEntity +{ + @Column('int', { name: 'sort_order', default: 0 }) + sort_order: number; + + @Column('varchar', { name: 'title', nullable: true }) + title: string; + + @Column('text', { name: 'description', nullable: true }) + description: string; +} diff --git a/src/modules/web-information/term-condition/data/services/term-condition-data.service.ts b/src/modules/web-information/term-condition/data/services/term-condition-data.service.ts new file mode 100644 index 0000000..607abb8 --- /dev/null +++ b/src/modules/web-information/term-condition/data/services/term-condition-data.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; +import { TermConditionEntity } from '../../domain/entities/term-condition.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { TermConditionModel } from '../models/term-condition.model'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { Repository } from 'typeorm'; + +@Injectable() +export class TermConditionDataService extends BaseDataService { + constructor( + @InjectRepository(TermConditionModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } + + async getSortColumn(): Promise { + const query = this.repo.createQueryBuilder('data'); + + const sortColumn = await query + .select('data.sort_order') + .orderBy('data.sort_order', 'DESC') + .getOne(); + + const lastColumn = sortColumn?.sort_order ?? 0; + return lastColumn + 1; + } +} diff --git a/src/modules/web-information/term-condition/data/services/term-condition-read.service.ts b/src/modules/web-information/term-condition/data/services/term-condition-read.service.ts new file mode 100644 index 0000000..98d4bc1 --- /dev/null +++ b/src/modules/web-information/term-condition/data/services/term-condition-read.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { TermConditionEntity } from '../../domain/entities/term-condition.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { TermConditionModel } from '../models/term-condition.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 TermConditionReadService extends BaseReadService { + constructor( + @InjectRepository(TermConditionModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/web-information/term-condition/domain/entities/event/term-condition-change-status.event.ts b/src/modules/web-information/term-condition/domain/entities/event/term-condition-change-status.event.ts new file mode 100644 index 0000000..80e32e1 --- /dev/null +++ b/src/modules/web-information/term-condition/domain/entities/event/term-condition-change-status.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class TermConditionChangeStatusEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/term-condition/domain/entities/event/term-condition-created.event.ts b/src/modules/web-information/term-condition/domain/entities/event/term-condition-created.event.ts new file mode 100644 index 0000000..0defc89 --- /dev/null +++ b/src/modules/web-information/term-condition/domain/entities/event/term-condition-created.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class TermConditionCreatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/term-condition/domain/entities/event/term-condition-deleted.event.ts b/src/modules/web-information/term-condition/domain/entities/event/term-condition-deleted.event.ts new file mode 100644 index 0000000..d2a108a --- /dev/null +++ b/src/modules/web-information/term-condition/domain/entities/event/term-condition-deleted.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class TermConditionDeletedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/term-condition/domain/entities/event/term-condition-updated.event.ts b/src/modules/web-information/term-condition/domain/entities/event/term-condition-updated.event.ts new file mode 100644 index 0000000..0bde406 --- /dev/null +++ b/src/modules/web-information/term-condition/domain/entities/event/term-condition-updated.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class TermConditionUpdatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/web-information/term-condition/domain/entities/filter-term-condition.entity.ts b/src/modules/web-information/term-condition/domain/entities/filter-term-condition.entity.ts new file mode 100644 index 0000000..903ed96 --- /dev/null +++ b/src/modules/web-information/term-condition/domain/entities/filter-term-condition.entity.ts @@ -0,0 +1,5 @@ +import { BaseFilterEntity } from 'src/core/modules/domain/entities/base-filter.entity'; + +export interface FilterTermConditionEntity extends BaseFilterEntity { + titles: string[]; +} diff --git a/src/modules/web-information/term-condition/domain/entities/term-condition.entity.ts b/src/modules/web-information/term-condition/domain/entities/term-condition.entity.ts new file mode 100644 index 0000000..c477277 --- /dev/null +++ b/src/modules/web-information/term-condition/domain/entities/term-condition.entity.ts @@ -0,0 +1,7 @@ +import { BaseStatusEntity } from 'src/core/modules/domain/entities/base-status.entity'; + +export interface TermConditionEntity extends BaseStatusEntity { + sort_order: number; + title: string; + description: string; +} diff --git a/src/modules/web-information/term-condition/domain/usecases/managers/active-term-condition.manager.ts b/src/modules/web-information/term-condition/domain/usecases/managers/active-term-condition.manager.ts new file mode 100644 index 0000000..9daee6f --- /dev/null +++ b/src/modules/web-information/term-condition/domain/usecases/managers/active-term-condition.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 { TermConditionEntity } from '../../entities/term-condition.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TermConditionModel } from '../../../data/models/term-condition.model'; +import { TermConditionChangeStatusEvent } from '../../entities/event/term-condition-change-status.event'; + +@Injectable() +export class ActiveTermConditionManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.title}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TermConditionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TermConditionChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/term-condition/domain/usecases/managers/batch-active-term-condition.manager.ts b/src/modules/web-information/term-condition/domain/usecases/managers/batch-active-term-condition.manager.ts new file mode 100644 index 0000000..cb85f8f --- /dev/null +++ b/src/modules/web-information/term-condition/domain/usecases/managers/batch-active-term-condition.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { TermConditionEntity } from '../../entities/term-condition.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TermConditionModel } from '../../../data/models/term-condition.model'; +import { TermConditionChangeStatusEvent } from '../../entities/event/term-condition-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchActiveTermConditionManager extends BaseBatchUpdateStatusManager { + validateData(data: TermConditionEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TermConditionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TermConditionChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/term-condition/domain/usecases/managers/batch-confirm-term-condition.manager.ts b/src/modules/web-information/term-condition/domain/usecases/managers/batch-confirm-term-condition.manager.ts new file mode 100644 index 0000000..5403de8 --- /dev/null +++ b/src/modules/web-information/term-condition/domain/usecases/managers/batch-confirm-term-condition.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { TermConditionEntity } from '../../entities/term-condition.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TermConditionModel } from '../../../data/models/term-condition.model'; +import { TermConditionChangeStatusEvent } from '../../entities/event/term-condition-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchConfirmTermConditionManager extends BaseBatchUpdateStatusManager { + validateData(data: TermConditionEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TermConditionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TermConditionChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/term-condition/domain/usecases/managers/batch-delete-term-condition.manager.ts b/src/modules/web-information/term-condition/domain/usecases/managers/batch-delete-term-condition.manager.ts new file mode 100644 index 0000000..7246193 --- /dev/null +++ b/src/modules/web-information/term-condition/domain/usecases/managers/batch-delete-term-condition.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchDeleteManager } from 'src/core/modules/domain/usecase/managers/base-batch-delete.manager'; +import { TermConditionEntity } from '../../entities/term-condition.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TermConditionModel } from '../../../data/models/term-condition.model'; +import { TermConditionDeletedEvent } from '../../entities/event/term-condition-deleted.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchDeleteTermConditionManager extends BaseBatchDeleteManager { + async beforeProcess(): Promise { + return; + } + + async validateData(data: TermConditionEntity): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TermConditionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TermConditionDeletedEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/term-condition/domain/usecases/managers/batch-inactive-term-condition.manager.ts b/src/modules/web-information/term-condition/domain/usecases/managers/batch-inactive-term-condition.manager.ts new file mode 100644 index 0000000..b234d43 --- /dev/null +++ b/src/modules/web-information/term-condition/domain/usecases/managers/batch-inactive-term-condition.manager.ts @@ -0,0 +1,45 @@ +import { BaseBatchUpdateStatusManager } from 'src/core/modules/domain/usecase/managers/base-batch-update-status.manager'; +import { TermConditionEntity } from '../../entities/term-condition.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TermConditionModel } from '../../../data/models/term-condition.model'; +import { TermConditionChangeStatusEvent } from '../../entities/event/term-condition-change-status.event'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class BatchInactiveTermConditionManager extends BaseBatchUpdateStatusManager { + validateData(data: TermConditionEntity): Promise { + return; + } + + beforeProcess(): Promise { + return; + } + + afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TermConditionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TermConditionChangeStatusEvent, + }, + ]; + } + + getResult(): BatchResult { + return this.result; + } +} diff --git a/src/modules/web-information/term-condition/domain/usecases/managers/change-position-term-condition.manager.ts b/src/modules/web-information/term-condition/domain/usecases/managers/change-position-term-condition.manager.ts new file mode 100644 index 0000000..ff7d1a3 --- /dev/null +++ b/src/modules/web-information/term-condition/domain/usecases/managers/change-position-term-condition.manager.ts @@ -0,0 +1,15 @@ +import { Injectable } from '@nestjs/common'; +import { BaseChangePosition } from 'src/core/modules/domain/usecase/managers/base-change-position.manager'; +import { TermConditionEntity } from '../../entities/term-condition.entity'; +import { TermConditionModel } from '../../../data/models/term-condition.model'; + +@Injectable() +export class ChangePositionTermConditionManager extends BaseChangePosition { + get entityTarget(): any { + return TermConditionModel; + } + + async afterProcess(): Promise { + return; + } +} diff --git a/src/modules/web-information/term-condition/domain/usecases/managers/confirm-term-condition.manager.ts b/src/modules/web-information/term-condition/domain/usecases/managers/confirm-term-condition.manager.ts new file mode 100644 index 0000000..7a0adfc --- /dev/null +++ b/src/modules/web-information/term-condition/domain/usecases/managers/confirm-term-condition.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 { TermConditionEntity } from '../../entities/term-condition.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TermConditionModel } from '../../../data/models/term-condition.model'; +import { TermConditionChangeStatusEvent } from '../../entities/event/term-condition-change-status.event'; + +@Injectable() +export class ConfirmTermConditionManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success active data ${this.result.title}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TermConditionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TermConditionChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/term-condition/domain/usecases/managers/create-term-condition.manager.ts b/src/modules/web-information/term-condition/domain/usecases/managers/create-term-condition.manager.ts new file mode 100644 index 0000000..2a0d2ef --- /dev/null +++ b/src/modules/web-information/term-condition/domain/usecases/managers/create-term-condition.manager.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@nestjs/common'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TermConditionEntity } from '../../entities/term-condition.entity'; +import { TermConditionModel } from '../../../data/models/term-condition.model'; +import { BaseCreateManager } from 'src/core/modules/domain/usecase/managers/base-create.manager'; +import { TermConditionCreatedEvent } from '../../entities/event/term-condition-created.event'; + +@Injectable() +export class CreateTermConditionManager extends BaseCreateManager { + async beforeProcess(): Promise { + const sortColumn = await this.dataService.getSortColumn(); + + Object.assign(this.data, { + sort_order: sortColumn, + }); + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TermConditionCreatedEvent, + data: this.data, + }, + ]; + } + + get entityTarget(): any { + return TermConditionModel; + } +} diff --git a/src/modules/web-information/term-condition/domain/usecases/managers/delete-term-condition.manager.ts b/src/modules/web-information/term-condition/domain/usecases/managers/delete-term-condition.manager.ts new file mode 100644 index 0000000..7732a48 --- /dev/null +++ b/src/modules/web-information/term-condition/domain/usecases/managers/delete-term-condition.manager.ts @@ -0,0 +1,45 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDeleteManager } from 'src/core/modules/domain/usecase/managers/base-delete.manager'; +import { TermConditionEntity } from '../../entities/term-condition.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TermConditionModel } from '../../../data/models/term-condition.model'; +import { TermConditionDeletedEvent } from '../../entities/event/term-condition-deleted.event'; + +@Injectable() +export class DeleteTermConditionManager 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 TermConditionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TermConditionDeletedEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/term-condition/domain/usecases/managers/detail-term-condition.manager.ts b/src/modules/web-information/term-condition/domain/usecases/managers/detail-term-condition.manager.ts new file mode 100644 index 0000000..6810339 --- /dev/null +++ b/src/modules/web-information/term-condition/domain/usecases/managers/detail-term-condition.manager.ts @@ -0,0 +1,51 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDetailManager } from 'src/core/modules/domain/usecase/managers/base-detail.manager'; +import { TermConditionEntity } from '../../entities/term-condition.entity'; +import { RelationParam } from 'src/core/modules/domain/entities/base-filter.entity'; + +@Injectable() +export class DetailTermConditionManager extends BaseDetailManager { + async prepareData(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + // relation only join (for query purpose) + joinRelations: [], + + // relation join and select (relasi yang ingin ditampilkan), + selectRelations: [], + + // relation yang hanya ingin dihitung (akan return number) + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.status`, + `${this.tableName}.created_at`, + `${this.tableName}.creator_name`, + `${this.tableName}.updated_at`, + `${this.tableName}.editor_name`, + `${this.tableName}.title`, + `${this.tableName}.description`, + ]; + } + + get setFindProperties(): any { + return { + id: this.dataId, + }; + } +} diff --git a/src/modules/web-information/term-condition/domain/usecases/managers/inactive-term-condition.manager.ts b/src/modules/web-information/term-condition/domain/usecases/managers/inactive-term-condition.manager.ts new file mode 100644 index 0000000..5fa1432 --- /dev/null +++ b/src/modules/web-information/term-condition/domain/usecases/managers/inactive-term-condition.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 { TermConditionEntity } from '../../entities/term-condition.entity'; +import { + EventTopics, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; +import { TermConditionModel } from '../../../data/models/term-condition.model'; +import { TermConditionChangeStatusEvent } from '../../entities/event/term-condition-change-status.event'; + +@Injectable() +export class InactiveTermConditionManager extends BaseUpdateStatusManager { + getResult(): string { + return `Success inactive data ${this.result.title}`; + } + + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get entityTarget(): any { + return TermConditionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TermConditionChangeStatusEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/term-condition/domain/usecases/managers/index-term-condition.manager.ts b/src/modules/web-information/term-condition/domain/usecases/managers/index-term-condition.manager.ts new file mode 100644 index 0000000..0680b9f --- /dev/null +++ b/src/modules/web-information/term-condition/domain/usecases/managers/index-term-condition.manager.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@nestjs/common'; +import { BaseIndexManager } from 'src/core/modules/domain/usecase/managers/base-index.manager'; +import { TermConditionEntity } from '../../entities/term-condition.entity'; +import { SelectQueryBuilder } from 'typeorm'; +import { + Param, + RelationParam, +} from 'src/core/modules/domain/entities/base-filter.entity'; +import { ORDER_TYPE } from 'src/core/strings/constants/base.constants'; + +@Injectable() +export class IndexTermConditionManager extends BaseIndexManager { + async prepareData(): Promise { + this.filterParam.order_by = `${this.tableName}.sort_order`; + this.filterParam.order_type = ORDER_TYPE.ASC; + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get relations(): RelationParam { + return { + // relation only join (for query purpose) + joinRelations: [], + + // relation join and select (relasi yang ingin ditampilkan), + selectRelations: [], + + // relation yang hanya ingin dihitung (akan return number) + countRelations: [], + }; + } + + get selects(): string[] { + return [ + `${this.tableName}.id`, + `${this.tableName}.status`, + `${this.tableName}.sort_order`, + `${this.tableName}.created_at`, + `${this.tableName}.creator_name`, + `${this.tableName}.updated_at`, + `${this.tableName}.editor_name`, + `${this.tableName}.title`, + `${this.tableName}.description`, + ]; + } + + get specificFilter(): Param[] { + return [ + { + cols: `${this.tableName}.title`, + data: this.filterParam.titles, + }, + ]; + } + + setQueryFilter( + queryBuilder: SelectQueryBuilder, + ): SelectQueryBuilder { + return queryBuilder; + } +} diff --git a/src/modules/web-information/term-condition/domain/usecases/managers/update-term-condition.manager.ts b/src/modules/web-information/term-condition/domain/usecases/managers/update-term-condition.manager.ts new file mode 100644 index 0000000..8c08145 --- /dev/null +++ b/src/modules/web-information/term-condition/domain/usecases/managers/update-term-condition.manager.ts @@ -0,0 +1,46 @@ +import { Injectable } from '@nestjs/common'; +import { BaseUpdateManager } from 'src/core/modules/domain/usecase/managers/base-update.manager'; +import { TermConditionEntity } from '../../entities/term-condition.entity'; +import { TermConditionModel } from '../../../data/models/term-condition.model'; +import { TermConditionUpdatedEvent } from '../../entities/event/term-condition-updated.event'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; + +@Injectable() +export class UpdateTermConditionManager extends BaseUpdateManager { + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get entityTarget(): any { + return TermConditionModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: TermConditionUpdatedEvent, + data: this.data, + }, + ]; + } +} diff --git a/src/modules/web-information/term-condition/domain/usecases/term-condition-data.orchestrator.ts b/src/modules/web-information/term-condition/domain/usecases/term-condition-data.orchestrator.ts new file mode 100644 index 0000000..e7d8dd3 --- /dev/null +++ b/src/modules/web-information/term-condition/domain/usecases/term-condition-data.orchestrator.ts @@ -0,0 +1,133 @@ +import { Injectable } from '@nestjs/common'; +import { CreateTermConditionManager } from './managers/create-term-condition.manager'; +import { TermConditionDataService } from '../../data/services/term-condition-data.service'; +import { TermConditionEntity } from '../entities/term-condition.entity'; +import { DeleteTermConditionManager } from './managers/delete-term-condition.manager'; +import { UpdateTermConditionManager } from './managers/update-term-condition.manager'; +import { BaseDataTransactionOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-data-transaction.orchestrator'; +import { ActiveTermConditionManager } from './managers/active-term-condition.manager'; +import { InactiveTermConditionManager } from './managers/inactive-term-condition.manager'; +import { ConfirmTermConditionManager } from './managers/confirm-term-condition.manager'; +import { STATUS } from 'src/core/strings/constants/base.constants'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchConfirmTermConditionManager } from './managers/batch-confirm-term-condition.manager'; +import { BatchInactiveTermConditionManager } from './managers/batch-inactive-term-condition.manager'; +import { BatchActiveTermConditionManager } from './managers/batch-active-term-condition.manager'; +import { BatchDeleteTermConditionManager } from './managers/batch-delete-term-condition.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { ChangePositionTermConditionManager } from './managers/change-position-term-condition.manager'; + +@Injectable() +export class TermConditionDataOrchestrator extends BaseDataTransactionOrchestrator { + constructor( + private createManager: CreateTermConditionManager, + private updateManager: UpdateTermConditionManager, + private deleteManager: DeleteTermConditionManager, + private activeManager: ActiveTermConditionManager, + private confirmManager: ConfirmTermConditionManager, + private inactiveManager: InactiveTermConditionManager, + private batchDeleteManager: BatchDeleteTermConditionManager, + private batchActiveManager: BatchActiveTermConditionManager, + private batchConfirmManager: BatchConfirmTermConditionManager, + private batchInactiveManager: BatchInactiveTermConditionManager, + private changePositionManager: ChangePositionTermConditionManager, + private serviceData: TermConditionDataService, + ) { + super(); + } + + async changePostion(data): Promise { + this.changePositionManager.setData(data, 'sort_order'); + this.changePositionManager.setService( + this.serviceData, + TABLE_NAME.TERM_CONDITION, + ); + await this.changePositionManager.execute(); + return this.changePositionManager.getResult(); + } + + async create(data): Promise { + this.createManager.setData(data); + this.createManager.setService(this.serviceData, TABLE_NAME.TERM_CONDITION); + await this.createManager.execute(); + return this.createManager.getResult(); + } + + async update(dataId, data): Promise { + this.updateManager.setData(dataId, data); + this.updateManager.setService(this.serviceData, TABLE_NAME.TERM_CONDITION); + await this.updateManager.execute(); + return this.updateManager.getResult(); + } + + async delete(dataId): Promise { + this.deleteManager.setData(dataId); + this.deleteManager.setService(this.serviceData, TABLE_NAME.TERM_CONDITION); + await this.deleteManager.execute(); + return this.deleteManager.getResult(); + } + + async batchDelete(dataIds: string[]): Promise { + this.batchDeleteManager.setData(dataIds); + this.batchDeleteManager.setService( + this.serviceData, + TABLE_NAME.TERM_CONDITION, + ); + 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.TERM_CONDITION); + 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.TERM_CONDITION, + ); + 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.TERM_CONDITION); + 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.TERM_CONDITION, + ); + 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.TERM_CONDITION, + ); + 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.TERM_CONDITION, + ); + await this.batchInactiveManager.execute(); + return this.batchInactiveManager.getResult(); + } +} diff --git a/src/modules/web-information/term-condition/domain/usecases/term-condition-read.orchestrator.ts b/src/modules/web-information/term-condition/domain/usecases/term-condition-read.orchestrator.ts new file mode 100644 index 0000000..00427fd --- /dev/null +++ b/src/modules/web-information/term-condition/domain/usecases/term-condition-read.orchestrator.ts @@ -0,0 +1,33 @@ +import { Injectable } from '@nestjs/common'; +import { IndexTermConditionManager } from './managers/index-term-condition.manager'; +import { TermConditionReadService } from '../../data/services/term-condition-read.service'; +import { TermConditionEntity } from '../entities/term-condition.entity'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { BaseReadOrchestrator } from 'src/core/modules/domain/usecase/orchestrators/base-read.orchestrator'; +import { DetailTermConditionManager } from './managers/detail-term-condition.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@Injectable() +export class TermConditionReadOrchestrator extends BaseReadOrchestrator { + constructor( + private indexManager: IndexTermConditionManager, + private detailManager: DetailTermConditionManager, + private serviceData: TermConditionReadService, + ) { + super(); + } + + async index(params): Promise> { + this.indexManager.setFilterParam(params); + this.indexManager.setService(this.serviceData, TABLE_NAME.TERM_CONDITION); + await this.indexManager.execute(); + return this.indexManager.getResult(); + } + + async detail(dataId: string): Promise { + this.detailManager.setData(dataId); + this.detailManager.setService(this.serviceData, TABLE_NAME.TERM_CONDITION); + await this.detailManager.execute(); + return this.detailManager.getResult(); + } +} diff --git a/src/modules/web-information/term-condition/index.ts b/src/modules/web-information/term-condition/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/web-information/term-condition/infrastructure/dto/filter-term-condition.dto.ts b/src/modules/web-information/term-condition/infrastructure/dto/filter-term-condition.dto.ts new file mode 100644 index 0000000..e68909b --- /dev/null +++ b/src/modules/web-information/term-condition/infrastructure/dto/filter-term-condition.dto.ts @@ -0,0 +1,15 @@ +import { BaseFilterDto } from 'src/core/modules/infrastructure/dto/base-filter.dto'; +import { FilterTermConditionEntity } from '../../domain/entities/filter-term-condition.entity'; +import { Transform } from 'class-transformer'; +import { ApiProperty } from '@nestjs/swagger'; + +export class FilterTermConditionDto + extends BaseFilterDto + implements FilterTermConditionEntity +{ + @ApiProperty({ type: ['string'], required: false }) + @Transform((body) => { + return Array.isArray(body.value) ? body.value : [body.value]; + }) + titles: string[]; +} diff --git a/src/modules/web-information/term-condition/infrastructure/dto/term-condition.dto.ts b/src/modules/web-information/term-condition/infrastructure/dto/term-condition.dto.ts new file mode 100644 index 0000000..063a244 --- /dev/null +++ b/src/modules/web-information/term-condition/infrastructure/dto/term-condition.dto.ts @@ -0,0 +1,28 @@ +import { BaseStatusDto } from 'src/core/modules/infrastructure/dto/base-status.dto'; +import { TermConditionEntity } from '../../domain/entities/term-condition.entity'; +import { ApiProperty } from '@nestjs/swagger'; +import { IsString } from 'class-validator'; +import { Exclude } from 'class-transformer'; + +export class TermConditionDto + extends BaseStatusDto + implements TermConditionEntity +{ + @Exclude() + sort_order: number; + + @ApiProperty({ + type: String, + required: true, + example: 'Booking', + }) + @IsString() + title: string; + + @ApiProperty({ + type: String, + required: false, + example: 'Booking descs', + }) + description: string; +} diff --git a/src/modules/web-information/term-condition/infrastructure/term-condition-data.controller.ts b/src/modules/web-information/term-condition/infrastructure/term-condition-data.controller.ts new file mode 100644 index 0000000..592b4ad --- /dev/null +++ b/src/modules/web-information/term-condition/infrastructure/term-condition-data.controller.ts @@ -0,0 +1,84 @@ +import { + Body, + Controller, + Delete, + Param, + Patch, + Post, + Put, +} from '@nestjs/common'; +import { TermConditionDataOrchestrator } from '../domain/usecases/term-condition-data.orchestrator'; +import { TermConditionDto } from './dto/term-condition.dto'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { TermConditionEntity } from '../domain/entities/term-condition.entity'; +import { BatchResult } from 'src/core/response/domain/ok-response.interface'; +import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto'; +import { Public } from 'src/core/guards'; +import { ChangePositionDto } from 'src/core/modules/infrastructure/dto/base-change-position.dto'; + +@ApiTags(`${MODULE_NAME.TERM_CONDITION.split('-').join(' ')} - data`) +@Controller(`v1/${MODULE_NAME.TERM_CONDITION}`) +@Public(false) +@ApiBearerAuth('JWT') +export class TermConditionDataController { + constructor(private orchestrator: TermConditionDataOrchestrator) {} + + @Post() + async create(@Body() data: TermConditionDto): Promise { + return await this.orchestrator.create(data); + } + + @Post('/change-position') + async dragDrop(@Body() body: ChangePositionDto): Promise { + return await this.orchestrator.changePostion(body); + } + + @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: TermConditionDto, + ): Promise { + return await this.orchestrator.update(dataId, data); + } + + @Delete(':id') + async delete(@Param('id') dataId: string): Promise { + return await this.orchestrator.delete(dataId); + } +} diff --git a/src/modules/web-information/term-condition/infrastructure/term-condition-read.controller.ts b/src/modules/web-information/term-condition/infrastructure/term-condition-read.controller.ts new file mode 100644 index 0000000..773b939 --- /dev/null +++ b/src/modules/web-information/term-condition/infrastructure/term-condition-read.controller.ts @@ -0,0 +1,30 @@ +import { Controller, Get, Param, Query } from '@nestjs/common'; +import { FilterTermConditionDto } from './dto/filter-term-condition.dto'; +import { Pagination } from 'src/core/response'; +import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; +import { TermConditionEntity } from '../domain/entities/term-condition.entity'; +import { TermConditionReadOrchestrator } from '../domain/usecases/term-condition-read.orchestrator'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; +import { Public } from 'src/core/guards'; + +@ApiTags(`${MODULE_NAME.TERM_CONDITION.split('-').join(' ')} - read`) +@Controller(`v1/${MODULE_NAME.TERM_CONDITION}`) +@Public(false) +@ApiBearerAuth('JWT') +export class TermConditionReadController { + constructor(private orchestrator: TermConditionReadOrchestrator) {} + + @Get() + @Pagination() + async index( + @Query() params: FilterTermConditionDto, + ): Promise> { + return await this.orchestrator.index(params); + } + + @Get(':id') + async detail(@Param('id') id: string): Promise { + return await this.orchestrator.detail(id); + } +} diff --git a/src/modules/web-information/term-condition/term-condition.module.ts b/src/modules/web-information/term-condition/term-condition.module.ts new file mode 100644 index 0000000..7c110ed --- /dev/null +++ b/src/modules/web-information/term-condition/term-condition.module.ts @@ -0,0 +1,56 @@ +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 { TermConditionDataService } from './data/services/term-condition-data.service'; +import { TermConditionReadService } from './data/services/term-condition-read.service'; +import { TermConditionReadController } from './infrastructure/term-condition-read.controller'; +import { TermConditionReadOrchestrator } from './domain/usecases/term-condition-read.orchestrator'; +import { TermConditionDataController } from './infrastructure/term-condition-data.controller'; +import { TermConditionDataOrchestrator } from './domain/usecases/term-condition-data.orchestrator'; +import { CreateTermConditionManager } from './domain/usecases/managers/create-term-condition.manager'; +import { CqrsModule } from '@nestjs/cqrs'; +import { IndexTermConditionManager } from './domain/usecases/managers/index-term-condition.manager'; +import { DeleteTermConditionManager } from './domain/usecases/managers/delete-term-condition.manager'; +import { UpdateTermConditionManager } from './domain/usecases/managers/update-term-condition.manager'; +import { ActiveTermConditionManager } from './domain/usecases/managers/active-term-condition.manager'; +import { ConfirmTermConditionManager } from './domain/usecases/managers/confirm-term-condition.manager'; +import { InactiveTermConditionManager } from './domain/usecases/managers/inactive-term-condition.manager'; +import { DetailTermConditionManager } from './domain/usecases/managers/detail-term-condition.manager'; +import { BatchDeleteTermConditionManager } from './domain/usecases/managers/batch-delete-term-condition.manager'; +import { BatchActiveTermConditionManager } from './domain/usecases/managers/batch-active-term-condition.manager'; +import { BatchConfirmTermConditionManager } from './domain/usecases/managers/batch-confirm-term-condition.manager'; +import { BatchInactiveTermConditionManager } from './domain/usecases/managers/batch-inactive-term-condition.manager'; +import { TermConditionModel } from './data/models/term-condition.model'; +import { ChangePositionTermConditionManager } from './domain/usecases/managers/change-position-term-condition.manager'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature([TermConditionModel], CONNECTION_NAME.DEFAULT), + CqrsModule, + ], + controllers: [TermConditionDataController, TermConditionReadController], + providers: [ + IndexTermConditionManager, + DetailTermConditionManager, + CreateTermConditionManager, + DeleteTermConditionManager, + UpdateTermConditionManager, + ActiveTermConditionManager, + ConfirmTermConditionManager, + InactiveTermConditionManager, + BatchDeleteTermConditionManager, + BatchActiveTermConditionManager, + BatchConfirmTermConditionManager, + BatchInactiveTermConditionManager, + ChangePositionTermConditionManager, + + TermConditionDataService, + TermConditionReadService, + + TermConditionDataOrchestrator, + TermConditionReadOrchestrator, + ], +}) +export class TermConditionModule {} diff --git a/yarn.lock b/yarn.lock index 5de5fa7..640a173 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10,190 +10,194 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.24" -"@angular-devkit/core@17.1.2": - version "17.1.2" - resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-17.1.2.tgz#bf2c3475e9ff853dc53d8dc8ce9bbf8b2f1193f8" - integrity sha512-ku+/W/HMCBacSWFppenr9y6Lx8mDuTuQvn1IkTyBLiJOpWnzgVbx9kHDeaDchGa1PwLlJUBBrv27t3qgJOIDPw== +"@angular-devkit/core@17.3.8": + version "17.3.8" + resolved "https://registry.yarnpkg.com/@angular-devkit/core/-/core-17.3.8.tgz#8679cacf84cf79764f027811020e235ab32016d2" + integrity sha512-Q8q0voCGudbdCgJ7lXdnyaxKHbNQBARH68zPQV72WT8NWy+Gw/tys870i6L58NWbBaCJEUcIj/kb6KoakSRu+Q== dependencies: ajv "8.12.0" ajv-formats "2.1.1" - jsonc-parser "3.2.0" - picomatch "3.0.1" + jsonc-parser "3.2.1" + picomatch "4.0.1" rxjs "7.8.1" source-map "0.7.4" -"@angular-devkit/schematics-cli@17.1.2": - version "17.1.2" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics-cli/-/schematics-cli-17.1.2.tgz#7a77e8294071e5ba569e2ffb567b3301d1db3f07" - integrity sha512-bvXykYzSST05qFdlgIzUguNOb3z0hCa8HaTwtqdmQo9aFPf+P+/AC56I64t1iTchMjQtf3JrBQhYM25gUdcGbg== +"@angular-devkit/schematics-cli@17.3.8": + version "17.3.8" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics-cli/-/schematics-cli-17.3.8.tgz#26eeb9b581309be474868d01d9f87555760557c3" + integrity sha512-TjmiwWJarX7oqvNiRAroQ5/LeKUatxBOCNEuKXO/PV8e7pn/Hr/BqfFm+UcYrQoFdZplmtNAfqmbqgVziKvCpA== dependencies: - "@angular-devkit/core" "17.1.2" - "@angular-devkit/schematics" "17.1.2" + "@angular-devkit/core" "17.3.8" + "@angular-devkit/schematics" "17.3.8" ansi-colors "4.1.3" - inquirer "9.2.12" + inquirer "9.2.15" symbol-observable "4.0.0" yargs-parser "21.1.1" -"@angular-devkit/schematics@17.1.2": - version "17.1.2" - resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-17.1.2.tgz#ca77a86ed44ab227614aff6e1f7ce4f3cd0c6ded" - integrity sha512-8S9RuM8olFN/gwN+mjbuF1CwHX61f0i59EGXz9tXLnKRUTjsRR+8vVMTAmX0dvVAT5fJTG/T69X+HX7FeumdqA== +"@angular-devkit/schematics@17.3.8": + version "17.3.8" + resolved "https://registry.yarnpkg.com/@angular-devkit/schematics/-/schematics-17.3.8.tgz#f853eb21682aadfb6667e090b5b509fc95ce8442" + integrity sha512-QRVEYpIfgkprNHc916JlPuNbLzOgrm9DZalHasnLUz4P6g7pR21olb8YCyM2OTJjombNhya9ZpckcADU5Qyvlg== dependencies: - "@angular-devkit/core" "17.1.2" - jsonc-parser "3.2.0" - magic-string "0.30.5" + "@angular-devkit/core" "17.3.8" + jsonc-parser "3.2.1" + magic-string "0.30.8" ora "5.4.1" rxjs "7.8.1" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.6.tgz#ab88da19344445c3d8889af2216606d3329f3ef2" - integrity sha512-ZJhac6FkEd1yhG2AHOmfcXG4ceoLltoCVJjN5XsWN9BifBQr+cHJbWi0h68HZuSORq+3WtJ2z0hwF2NG1b5kcA== +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.16.7", "@babel/code-frame@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" + integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== dependencies: - "@babel/highlight" "^7.24.6" + "@babel/highlight" "^7.24.7" picocolors "^1.0.0" -"@babel/compat-data@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.6.tgz#b3600217688cabb26e25f8e467019e66d71b7ae2" - integrity sha512-aC2DGhBq5eEdyXWqrDInSqQjO0k8xtPRf5YylULqx8MCd6jBtzqfta/3ETMRpuKIc5hyswfO80ObyA1MvkCcUQ== +"@babel/compat-data@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.7.tgz#d23bbea508c3883ba8251fb4164982c36ea577ed" + integrity sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw== "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.23.9": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.6.tgz#8650e0e4b03589ebe886c4e4a60398db0a7ec787" - integrity sha512-qAHSfAdVyFmIvl0VHELib8xar7ONuSHrE2hLnsaWkYNTI68dmi1x8GYDhJjMI/e7XWal9QBlZkwbOnkcw7Z8gQ== + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.7.tgz#b676450141e0b52a3d43bc91da86aa608f950ac4" + integrity sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g== dependencies: "@ampproject/remapping" "^2.2.0" - "@babel/code-frame" "^7.24.6" - "@babel/generator" "^7.24.6" - "@babel/helper-compilation-targets" "^7.24.6" - "@babel/helper-module-transforms" "^7.24.6" - "@babel/helpers" "^7.24.6" - "@babel/parser" "^7.24.6" - "@babel/template" "^7.24.6" - "@babel/traverse" "^7.24.6" - "@babel/types" "^7.24.6" + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.24.7" + "@babel/helper-compilation-targets" "^7.24.7" + "@babel/helper-module-transforms" "^7.24.7" + "@babel/helpers" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/template" "^7.24.7" + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.24.6", "@babel/generator@^7.7.2": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.6.tgz#dfac82a228582a9d30c959fe50ad28951d4737a7" - integrity sha512-S7m4eNa6YAPJRHmKsLHIDJhNAGNKoWNiWefz1MBbpnt8g9lvMDl1hir4P9bo/57bQEmuwEhnRU/AMWsD0G/Fbg== +"@babel/generator@^7.24.7", "@babel/generator@^7.7.2": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.7.tgz#1654d01de20ad66b4b4d99c135471bc654c55e6d" + integrity sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA== dependencies: - "@babel/types" "^7.24.6" + "@babel/types" "^7.24.7" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" -"@babel/helper-compilation-targets@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.6.tgz#4a51d681f7680043d38e212715e2a7b1ad29cb51" - integrity sha512-VZQ57UsDGlX/5fFA7GkVPplZhHsVc+vuErWgdOiysI9Ksnw0Pbbd6pnPiR/mmJyKHgyIW0c7KT32gmhiF+cirg== +"@babel/helper-compilation-targets@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.7.tgz#4eb6c4a80d6ffeac25ab8cd9a21b5dfa48d503a9" + integrity sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg== dependencies: - "@babel/compat-data" "^7.24.6" - "@babel/helper-validator-option" "^7.24.6" + "@babel/compat-data" "^7.24.7" + "@babel/helper-validator-option" "^7.24.7" browserslist "^4.22.2" lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-environment-visitor@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.6.tgz#ac7ad5517821641550f6698dd5468f8cef78620d" - integrity sha512-Y50Cg3k0LKLMjxdPjIl40SdJgMB85iXn27Vk/qbHZCFx/o5XO3PSnpi675h1KEmmDb6OFArfd5SCQEQ5Q4H88g== - -"@babel/helper-function-name@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.6.tgz#cebdd063386fdb95d511d84b117e51fc68fec0c8" - integrity sha512-xpeLqeeRkbxhnYimfr2PC+iA0Q7ljX/d1eZ9/inYbmfG2jpl8Lu3DyXvpOAnrS5kxkfOWJjioIMQsaMBXFI05w== +"@babel/helper-environment-visitor@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz#4b31ba9551d1f90781ba83491dd59cf9b269f7d9" + integrity sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ== dependencies: - "@babel/template" "^7.24.6" - "@babel/types" "^7.24.6" + "@babel/types" "^7.24.7" -"@babel/helper-hoist-variables@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.6.tgz#8a7ece8c26756826b6ffcdd0e3cf65de275af7f9" - integrity sha512-SF/EMrC3OD7dSta1bLJIlrsVxwtd0UpjRJqLno6125epQMJ/kyFmpTT4pbvPbdQHzCHg+biQ7Syo8lnDtbR+uA== +"@babel/helper-function-name@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz#75f1e1725742f39ac6584ee0b16d94513da38dd2" + integrity sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA== dependencies: - "@babel/types" "^7.24.6" + "@babel/template" "^7.24.7" + "@babel/types" "^7.24.7" -"@babel/helper-module-imports@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.6.tgz#65e54ffceed6a268dc4ce11f0433b82cfff57852" - integrity sha512-a26dmxFJBF62rRO9mmpgrfTLsAuyHk4e1hKTUkD/fcMfynt8gvEKwQPQDVxWhca8dHoDck+55DFt42zV0QMw5g== +"@babel/helper-hoist-variables@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz#b4ede1cde2fd89436397f30dc9376ee06b0f25ee" + integrity sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ== dependencies: - "@babel/types" "^7.24.6" + "@babel/types" "^7.24.7" -"@babel/helper-module-transforms@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.6.tgz#22346ed9df44ce84dee850d7433c5b73fab1fe4e" - integrity sha512-Y/YMPm83mV2HJTbX1Qh2sjgjqcacvOlhbzdCCsSlblOKjSYmQqEbO6rUniWQyRo9ncyfjT8hnUjlG06RXDEmcA== +"@babel/helper-module-imports@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" + integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== dependencies: - "@babel/helper-environment-visitor" "^7.24.6" - "@babel/helper-module-imports" "^7.24.6" - "@babel/helper-simple-access" "^7.24.6" - "@babel/helper-split-export-declaration" "^7.24.6" - "@babel/helper-validator-identifier" "^7.24.6" + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.24.6", "@babel/helper-plugin-utils@^7.8.0": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.6.tgz#fa02a32410a15a6e8f8185bcbf608f10528d2a24" - integrity sha512-MZG/JcWfxybKwsA9N9PmtF2lOSFSEMVCpIRrbxccZFLJPrJciJdG/UhSh5W96GEteJI2ARqm5UAHxISwRDLSNg== - -"@babel/helper-simple-access@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.6.tgz#1d6e04d468bba4fc963b4906f6dac6286cfedff1" - integrity sha512-nZzcMMD4ZhmB35MOOzQuiGO5RzL6tJbsT37Zx8M5L/i9KSrukGXWTjLe1knIbb/RmxoJE9GON9soq0c0VEMM5g== +"@babel/helper-module-transforms@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.7.tgz#31b6c9a2930679498db65b685b1698bfd6c7daf8" + integrity sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ== dependencies: - "@babel/types" "^7.24.6" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-simple-access" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" -"@babel/helper-split-export-declaration@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.6.tgz#e830068f7ba8861c53b7421c284da30ae656d7a3" - integrity sha512-CvLSkwXGWnYlF9+J3iZUvwgAxKiYzK3BWuo+mLzD/MDGOZDj7Gq8+hqaOkMxmJwmlv0iu86uH5fdADd9Hxkymw== +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.8.0": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz#98c84fe6fe3d0d3ae7bfc3a5e166a46844feb2a0" + integrity sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg== + +"@babel/helper-simple-access@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3" + integrity sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg== dependencies: - "@babel/types" "^7.24.6" + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" -"@babel/helper-string-parser@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.6.tgz#28583c28b15f2a3339cfafafeaad42f9a0e828df" - integrity sha512-WdJjwMEkmBicq5T9fm/cHND3+UlFa2Yj8ALLgmoSQAJZysYbBjw+azChSGPN4DSPLXOcooGRvDwZWMcF/mLO2Q== - -"@babel/helper-validator-identifier@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.6.tgz#08bb6612b11bdec78f3feed3db196da682454a5e" - integrity sha512-4yA7s865JHaqUdRbnaxarZREuPTHrjpDT+pXoAZ1yhyo6uFnIEpS8VMu16siFOHDpZNKYv5BObhsB//ycbICyw== - -"@babel/helper-validator-option@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.6.tgz#59d8e81c40b7d9109ab7e74457393442177f460a" - integrity sha512-Jktc8KkF3zIkePb48QO+IapbXlSapOW9S+ogZZkcO6bABgYAxtZcjZ/O005111YLf+j4M84uEgwYoidDkXbCkQ== - -"@babel/helpers@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.6.tgz#cd124245299e494bd4e00edda0e4ea3545c2c176" - integrity sha512-V2PI+NqnyFu1i0GyTd/O/cTpxzQCYioSkUIRmgo7gFEHKKCg5w46+r/A6WeUR1+P3TeQ49dspGPNd/E3n9AnnA== +"@babel/helper-split-export-declaration@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz#83949436890e07fa3d6873c61a96e3bbf692d856" + integrity sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA== dependencies: - "@babel/template" "^7.24.6" - "@babel/types" "^7.24.6" + "@babel/types" "^7.24.7" -"@babel/highlight@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.6.tgz#6d610c1ebd2c6e061cade0153bf69b0590b7b3df" - integrity sha512-2YnuOp4HAk2BsBrJJvYCbItHx0zWscI1C3zgWkz+wDyD9I7GIVrfnLyrR4Y1VR+7p+chAEcrgRQYZAGIKMV7vQ== +"@babel/helper-string-parser@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz#4d2d0f14820ede3b9807ea5fc36dfc8cd7da07f2" + integrity sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg== + +"@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" + integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== + +"@babel/helper-validator-option@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz#24c3bb77c7a425d1742eec8fb433b5a1b38e62f6" + integrity sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw== + +"@babel/helpers@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.7.tgz#aa2ccda29f62185acb5d42fb4a3a1b1082107416" + integrity sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg== dependencies: - "@babel/helper-validator-identifier" "^7.24.6" + "@babel/template" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/highlight@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" + integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== + dependencies: + "@babel/helper-validator-identifier" "^7.24.7" chalk "^2.4.2" js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.6.tgz#5e030f440c3c6c78d195528c3b688b101a365328" - integrity sha512-eNZXdfU35nJC2h24RznROuOpO94h6x8sg9ju0tT9biNtLZ2vuP8SduLqqV+/8+cebSLV9SJEAN5Z3zQbJG/M+Q== +"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9", "@babel/parser@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.7.tgz#9a5226f92f0c5c8ead550b750f5608e766c8ce85" + integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw== "@babel/plugin-syntax-async-generators@^7.8.4": version "7.8.4" @@ -231,11 +235,11 @@ "@babel/helper-plugin-utils" "^7.8.0" "@babel/plugin-syntax-jsx@^7.7.2": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.6.tgz#bcca2964150437f88f65e3679e3d68762287b9c8" - integrity sha512-lWfvAIFNWMlCsU0DRUun2GpFwZdGTukLaHJqRh1JRb80NdAP5Sb1HDHB5X9P9OtgZHQl089UzQkpYlBq2VTPRw== + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz#39a1fa4a7e3d3d7f34e2acc6be585b718d30e02d" + integrity sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ== dependencies: - "@babel/helper-plugin-utils" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.7" "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" @@ -287,44 +291,51 @@ "@babel/helper-plugin-utils" "^7.14.5" "@babel/plugin-syntax-typescript@^7.7.2": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.6.tgz#769daf2982d60308bc83d8936eaecb7582463c87" - integrity sha512-TzCtxGgVTEJWWwcYwQhCIQ6WaKlo80/B+Onsk4RRCcYqpYGFcG9etPW94VToGte5AAcxRrhjPUFvUS3Y2qKi4A== + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz#58d458271b4d3b6bb27ee6ac9525acbb259bad1c" + integrity sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA== dependencies: - "@babel/helper-plugin-utils" "^7.24.6" + "@babel/helper-plugin-utils" "^7.24.7" -"@babel/template@^7.24.6", "@babel/template@^7.3.3": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.6.tgz#048c347b2787a6072b24c723664c8d02b67a44f9" - integrity sha512-3vgazJlLwNXi9jhrR1ef8qiB65L1RK90+lEQwv4OxveHnqC3BfmnHdgySwRLzf6akhlOYenT+b7AfWq+a//AHw== +"@babel/runtime@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" + integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== dependencies: - "@babel/code-frame" "^7.24.6" - "@babel/parser" "^7.24.6" - "@babel/types" "^7.24.6" + regenerator-runtime "^0.14.0" -"@babel/traverse@^7.24.6": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.6.tgz#0941ec50cdeaeacad0911eb67ae227a4f8424edc" - integrity sha512-OsNjaJwT9Zn8ozxcfoBc+RaHdj3gFmCmYoQLUII1o6ZrUwku0BMg80FoOTPx+Gi6XhcQxAYE4xyjPTo4SxEQqw== +"@babel/template@^7.24.7", "@babel/template@^7.3.3": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315" + integrity sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig== dependencies: - "@babel/code-frame" "^7.24.6" - "@babel/generator" "^7.24.6" - "@babel/helper-environment-visitor" "^7.24.6" - "@babel/helper-function-name" "^7.24.6" - "@babel/helper-hoist-variables" "^7.24.6" - "@babel/helper-split-export-declaration" "^7.24.6" - "@babel/parser" "^7.24.6" - "@babel/types" "^7.24.6" + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/traverse@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.7.tgz#de2b900163fa741721ba382163fe46a936c40cf5" + integrity sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.24.7" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-hoist-variables" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/types" "^7.24.7" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.6", "@babel/types@^7.3.3": - version "7.24.6" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.6.tgz#ba4e1f59870c10dc2fa95a274ac4feec23b21912" - integrity sha512-WaMsgi6Q8zMgMth93GvWPXkhAIEobfsIkLTacoVZoK1J0CevIPGYY2Vo5YvJGqyHqXM6P4ppOYGsIRU8MM9pFQ== +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.24.7", "@babel/types@^7.3.3": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.7.tgz#6027fe12bc1aa724cd32ab113fb7f1988f1f66f2" + integrity sha512-XEFXSlxiG5td2EJRe8vOmRbaXVgfcBlszKujvVmWIK/UpywWljQCfzAv3RQCGujWQ1RD4YYWEAqDXfuJiy8f5Q== dependencies: - "@babel/helper-string-parser" "^7.24.6" - "@babel/helper-validator-identifier" "^7.24.6" + "@babel/helper-string-parser" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" "@bcoe/v8-coverage@^0.2.3": @@ -364,9 +375,9 @@ eslint-visitor-keys "^3.3.0" "@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": - version "4.10.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" - integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== + version "4.11.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" + integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== "@eslint/eslintrc@^2.1.4": version "2.1.4" @@ -393,6 +404,31 @@ resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-8.4.1.tgz#5d5e8aee8fce48f5e189bf730ebd1f758f491451" integrity sha512-XQ3cU+Q8Uqmrbf2e0cIC/QN43sTBSC8KF12u29Mb47tWrt2hAgBXSgpZMj4Ao8Uk0iJcU99QsOCaIL8934obCg== +"@fast-csv/format@4.3.5": + version "4.3.5" + resolved "https://registry.yarnpkg.com/@fast-csv/format/-/format-4.3.5.tgz#90d83d1b47b6aaf67be70d6118f84f3e12ee1ff3" + integrity sha512-8iRn6QF3I8Ak78lNAa+Gdl5MJJBM5vRHivFtMRUWINdevNo00K7OXxS2PshawLKTejVwieIlPmK5YlLu6w4u8A== + dependencies: + "@types/node" "^14.0.1" + lodash.escaperegexp "^4.1.2" + lodash.isboolean "^3.0.3" + lodash.isequal "^4.5.0" + lodash.isfunction "^3.0.9" + lodash.isnil "^4.0.0" + +"@fast-csv/parse@4.3.6": + version "4.3.6" + resolved "https://registry.yarnpkg.com/@fast-csv/parse/-/parse-4.3.6.tgz#ee47d0640ca0291034c7aa94039a744cfb019264" + integrity sha512-uRsLYksqpbDmWaSmzvJcuApSEe38+6NQZBUsuAyMZKqHxH0g1wcJgsKUvN3WC8tewaqFjBMMGrkHmC+T7k8LvA== + dependencies: + "@types/node" "^14.0.1" + lodash.escaperegexp "^4.1.2" + lodash.groupby "^4.6.0" + lodash.isfunction "^3.0.9" + lodash.isnil "^4.0.0" + lodash.isundefined "^3.0.1" + lodash.uniq "^4.5.0" + "@humanwhocodes/config-array@^0.11.14": version "0.11.14" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" @@ -412,10 +448,10 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz#4a2868d75d6d6963e423bcf90b7fd1be343409d3" integrity sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA== -"@inquirer/figures@^1.0.2": - version "1.0.2" - resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.2.tgz#a6af5e9f9969efb9ed3469130566315c36506b8a" - integrity sha512-4F1MBwVr3c/m4bAUef6LgkvBfSjzwH+OfldgHqcuacWwSUetFebM2wi58WfG9uk1rR98U6GwLed4asLJbwdV5w== +"@inquirer/figures@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@inquirer/figures/-/figures-1.0.3.tgz#1227cc980f88e6d6ab85abadbf164f5038041edd" + integrity sha512-ErXXzENMH5pJt5/ssXV0DfWUZqly8nGzf0UcBV9xTnP+KyffE2mqyxIMBrZ8ijQck2nU0TQm40EQB53YreyWHw== "@isaacs/cliui@^8.0.2": version "8.0.2" @@ -665,9 +701,9 @@ "@jridgewell/trace-mapping" "^0.3.25" "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": - version "1.4.15" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" - integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== "@jridgewell/trace-mapping@0.3.9": version "0.3.9" @@ -685,7 +721,7 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" -"@ljharb/through@^2.3.11", "@ljharb/through@^2.3.13": +"@ljharb/through@^2.3.12": version "2.3.13" resolved "https://registry.yarnpkg.com/@ljharb/through/-/through-2.3.13.tgz#b7e4766e0b65aa82e529be945ab078de79874edc" integrity sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ== @@ -712,69 +748,65 @@ semver "^7.3.5" tar "^6.1.11" -"@microsoft/tsdoc@^0.14.2": - version "0.14.2" - resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz#c3ec604a0b54b9a9b87e9735dfc59e1a5da6a5fb" - integrity sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug== +"@microsoft/tsdoc@^0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.15.0.tgz#f29a55df17cb6e87cfbabce33ff6a14a9f85076d" + integrity sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA== "@nestjs/cli@^10.0.0": - version "10.3.2" - resolved "https://registry.yarnpkg.com/@nestjs/cli/-/cli-10.3.2.tgz#42d2764ead6633e278c55d42de871b4cc1db002b" - integrity sha512-aWmD1GLluWrbuC4a1Iz/XBk5p74Uj6nIVZj6Ov03JbTfgtWqGFLtXuMetvzMiHxfrHehx/myt2iKAPRhKdZvTg== + version "10.4.2" + resolved "https://registry.yarnpkg.com/@nestjs/cli/-/cli-10.4.2.tgz#b71c9aacbdd92cebd81add5c4a4bf60b5a184c98" + integrity sha512-fQexIfLHfp6GUgX+CO4fOg+AEwV5ox/LHotQhyZi9wXUQDyIqS0NTTbumr//62EcX35qV4nU0359nYnuEdzG+A== dependencies: - "@angular-devkit/core" "17.1.2" - "@angular-devkit/schematics" "17.1.2" - "@angular-devkit/schematics-cli" "17.1.2" + "@angular-devkit/core" "17.3.8" + "@angular-devkit/schematics" "17.3.8" + "@angular-devkit/schematics-cli" "17.3.8" "@nestjs/schematics" "^10.0.1" chalk "4.1.2" chokidar "3.6.0" - cli-table3 "0.6.3" + cli-table3 "0.6.5" commander "4.1.1" fork-ts-checker-webpack-plugin "9.0.2" - glob "10.3.10" + glob "10.4.2" inquirer "8.2.6" node-emoji "1.11.0" ora "5.4.1" - rimraf "4.4.1" - shelljs "0.8.5" - source-map-support "0.5.21" tree-kill "1.2.2" tsconfig-paths "4.2.0" tsconfig-paths-webpack-plugin "4.1.0" typescript "5.3.3" - webpack "5.90.1" + webpack "5.92.1" webpack-node-externals "3.0.0" "@nestjs/common@^10.0.0": - version "10.3.8" - resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.3.8.tgz#2dada4dc8b53aa1630d00bdea57db4453f066c4b" - integrity sha512-P+vPEIvqx2e+fonsYVlFXKvoChyJ8Tq+lfpqdVFqblovHbFr3kZ/nYX0cPs+XuW6bnRT8tz0SSR9XBGU43kJhw== + version "10.3.10" + resolved "https://registry.yarnpkg.com/@nestjs/common/-/common-10.3.10.tgz#d8825d55a50a04e33080c9188e6a5b03235d19f2" + integrity sha512-H8k0jZtxk1IdtErGDmxFRy0PfcOAUg41Prrqpx76DQusGGJjsaovs1zjXVD1rZWaVYchfT1uczJ6L4Kio10VNg== dependencies: uid "2.0.2" iterare "1.2.1" - tslib "2.6.2" + tslib "2.6.3" "@nestjs/config@^3.2.2": - version "3.2.2" - resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-3.2.2.tgz#7e895edb2878564e0a7fc411614902b0330ca940" - integrity sha512-vGICPOui5vE6kPz1iwQ7oCnp3qWgqxldPmBQ9onkVoKlBtyc83KJCr7CjuVtf4OdovMAVcux1d8Q6jglU2ZphA== + version "3.2.3" + resolved "https://registry.yarnpkg.com/@nestjs/config/-/config-3.2.3.tgz#569888a33ada50b0f182002015e152e054990016" + integrity sha512-p6yv/CvoBewJ72mBq4NXgOAi2rSQNWx3a+IMJLVKS2uiwFCOQQuiIatGwq6MRjXV3Jr+B41iUO8FIf4xBrZ4/w== dependencies: dotenv "16.4.5" dotenv-expand "10.0.0" lodash "4.17.21" - uuid "9.0.1" "@nestjs/core@^10.0.0": - version "10.3.8" - resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-10.3.8.tgz#0831fc44b51cfe736cf5ffacd17d479dc806eddb" - integrity sha512-AxF4tpYLDNn5Wfb3C4bNaaHJ4pREH5FJrSisR2A5zkYpQFORFs0Tc36lOFPMwBTy8Iv2wUwWLUVc5ftBnxEv4w== + version "10.3.10" + resolved "https://registry.yarnpkg.com/@nestjs/core/-/core-10.3.10.tgz#508090c3ca36488a8e24a9e5939c2f37426e48f4" + integrity sha512-ZbQ4jovQyzHtCGCrzK5NdtW1SYO2fHSsgSY1+/9WdruYCUra+JDkWEXgZ4M3Hv480Dl3OXehAmY1wCOojeMyMQ== dependencies: uid "2.0.2" "@nuxtjs/opencollective" "0.3.2" fast-safe-stringify "2.1.1" iterare "1.2.1" path-to-regexp "3.2.0" - tslib "2.6.2" + tslib "2.6.3" "@nestjs/cqrs@^10.2.7": version "10.2.7" @@ -797,45 +829,53 @@ integrity sha512-bSJv4pd6EY99NX9CjBIyn4TVDoSit82DUZlL4I3bqNfy5Gt+gXTa86i3I/i0iIV9P4hntcGM5GyO+FhZAhxtyg== "@nestjs/platform-express@^10.0.0": - version "10.3.8" - resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.3.8.tgz#e8458cb1d1931589d5438d7b6075aa31634417d3" - integrity sha512-sifLoxgEJvAgbim1UuW6wyScMfkS9SVQRH+lN33N/9ZvZSjO6NSDLOe+wxqsnZkia+QrjFC0qy0ITRAsggfqbg== + version "10.3.10" + resolved "https://registry.yarnpkg.com/@nestjs/platform-express/-/platform-express-10.3.10.tgz#45fa006605913d5aaa31bf99073136ad639939b4" + integrity sha512-wK2ow3CZI2KFqWeEpPmoR300OB6BcBLxARV1EiClJLCj4S1mZsoCmS0YWgpk3j1j6mo0SI8vNLi/cC2iZPEPQA== dependencies: body-parser "1.20.2" cors "2.8.5" express "4.19.2" multer "1.4.4-lts.1" - tslib "2.6.2" + tslib "2.6.3" + +"@nestjs/schedule@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@nestjs/schedule/-/schedule-4.1.0.tgz#b0ae64519365821f4186416915e502d225836048" + integrity sha512-WEc96WTXZW+VI/Ng+uBpiBUwm6TWtAbQ4RKWkfbmzKvmbRGzA/9k/UyAWDS9k0pp+ZcbC+MaZQtt7TjQHrwX6g== + dependencies: + cron "3.1.7" + uuid "10.0.0" "@nestjs/schematics@^10.0.0", "@nestjs/schematics@^10.0.1": - version "10.1.1" - resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-10.1.1.tgz#a67fb178a7ad6025ccc3314910b077ac454fcdf3" - integrity sha512-o4lfCnEeIkfJhGBbLZxTuVWcGuqDCFwg5OrvpgRUBM7vI/vONvKKiB5riVNpO+JqXoH0I42NNeDb0m4V5RREig== + version "10.1.2" + resolved "https://registry.yarnpkg.com/@nestjs/schematics/-/schematics-10.1.2.tgz#690e675687ab5b4d63b59e888e20ebdf7231edbc" + integrity sha512-S0bMtZM5U4mAiqkhRyZkXgjmOHBS5P/lp/vEydgMR4F7csOShc3jFeKVs1Eghd9xCFezGKy3SHy7hFT6dpPhWQ== dependencies: - "@angular-devkit/core" "17.1.2" - "@angular-devkit/schematics" "17.1.2" + "@angular-devkit/core" "17.3.8" + "@angular-devkit/schematics" "17.3.8" comment-json "4.2.3" - jsonc-parser "3.2.1" + jsonc-parser "3.3.1" pluralize "8.0.0" "@nestjs/swagger@^7.3.1": - version "7.3.1" - resolved "https://registry.yarnpkg.com/@nestjs/swagger/-/swagger-7.3.1.tgz#353fdd5bd6f23564505117b1c82d7decc145e8fe" - integrity sha512-LUC4mr+5oAleEC/a2j8pNRh1S5xhKXJ1Gal5ZdRjt9XebQgbngXCdW7JTA9WOEcwGtFZN9EnKYdquzH971LZfw== + version "7.4.0" + resolved "https://registry.yarnpkg.com/@nestjs/swagger/-/swagger-7.4.0.tgz#e61dbefdfc1d4011327a256896953c74e511c850" + integrity sha512-dCiwKkRxcR7dZs5jtrGspBAe/nqJd1AYzOBTzw9iCdbq3BGrLpwokelk6lFZPe4twpTsPQqzNKBwKzVbI6AR/g== dependencies: - "@microsoft/tsdoc" "^0.14.2" + "@microsoft/tsdoc" "^0.15.0" "@nestjs/mapped-types" "2.0.5" js-yaml "4.1.0" lodash "4.17.21" path-to-regexp "3.2.0" - swagger-ui-dist "5.11.2" + swagger-ui-dist "5.17.14" "@nestjs/testing@^10.0.0": - version "10.3.8" - resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-10.3.8.tgz#44df73ede43c47801400d59a8ebd6ab1fe7df34c" - integrity sha512-hpX9das2TdFTKQ4/2ojhjI6YgXtCfXRKui3A4Qaj54VVzc5+mtK502Jj18Vzji98o9MVS6skmYu+S/UvW3U6Fw== + version "10.3.10" + resolved "https://registry.yarnpkg.com/@nestjs/testing/-/testing-10.3.10.tgz#6ed3c821fddf868665cc5ddc8591ee6eaab8a916" + integrity sha512-i3HAtVQJijxNxJq1k39aelyJlyEIBRONys7IipH/4r8W0J+M1V+y5EKDOyi4j1SdNSb/vmNyWpZ2/ewZjl3kRA== dependencies: - tslib "2.6.2" + tslib "2.6.3" "@nestjs/typeorm@^10.0.2": version "10.0.2" @@ -875,38 +915,38 @@ node-fetch "^2.6.1" "@opentelemetry/api@^1.4.1": - version "1.8.0" - resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.8.0.tgz#5aa7abb48f23f693068ed2999ae627d2f7d902ec" - integrity sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w== + version "1.9.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" + integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== -"@opentelemetry/core@1.24.1", "@opentelemetry/core@^1.11.0": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.24.1.tgz#35ab9d2ac9ca938e0ffbdfa40c49c169ac8ba80d" - integrity sha512-wMSGfsdmibI88K9wB498zXY04yThPexo8jvwNNlm542HZB7XrrMRBbAyKJqG8qDRJwIBdBrPMi4V9ZPW/sqrcg== +"@opentelemetry/core@1.25.1", "@opentelemetry/core@^1.11.0": + version "1.25.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.25.1.tgz#ff667d939d128adfc7c793edae2f6bca177f829d" + integrity sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ== dependencies: - "@opentelemetry/semantic-conventions" "1.24.1" + "@opentelemetry/semantic-conventions" "1.25.1" -"@opentelemetry/resources@1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.24.1.tgz#5e2cb84814824f3b1e1017e6caeeee8402e0ad6e" - integrity sha512-cyv0MwAaPF7O86x5hk3NNgenMObeejZFLJJDVuSeSMIsknlsj3oOZzRv3qSzlwYomXsICfBeFFlxwHQte5mGXQ== +"@opentelemetry/resources@1.25.1": + version "1.25.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.25.1.tgz#bb9a674af25a1a6c30840b755bc69da2796fefbb" + integrity sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ== dependencies: - "@opentelemetry/core" "1.24.1" - "@opentelemetry/semantic-conventions" "1.24.1" + "@opentelemetry/core" "1.25.1" + "@opentelemetry/semantic-conventions" "1.25.1" "@opentelemetry/sdk-metrics@^1.12.0": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-1.24.1.tgz#82ee3069b2ca9bb7c1e91272ff81536dc2e9bc8d" - integrity sha512-FrAqCbbGao9iKI+Mgh+OsC9+U2YMoXnlDHe06yH7dvavCKzE3S892dGtX54+WhSFVxHR/TMRVJiK/CV93GR0TQ== + version "1.25.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-1.25.1.tgz#50c985ec15557a9654334e7fa1018dc47a8a56b7" + integrity sha512-9Mb7q5ioFL4E4dDrc4wC/A3NTHDat44v4I3p2pLPSxRvqUbDIQyMVr9uK+EU69+HWhlET1VaSrRzwdckWqY15Q== dependencies: - "@opentelemetry/core" "1.24.1" - "@opentelemetry/resources" "1.24.1" + "@opentelemetry/core" "1.25.1" + "@opentelemetry/resources" "1.25.1" lodash.merge "^4.6.2" -"@opentelemetry/semantic-conventions@1.24.1": - version "1.24.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.24.1.tgz#d4bcebda1cb5146d47a2a53daaa7922f8e084dfb" - integrity sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw== +"@opentelemetry/semantic-conventions@1.25.1": + version "1.25.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.25.1.tgz#0deecb386197c5e9c2c28f2f89f51fb8ae9f145e" + integrity sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ== "@pkgjs/parseargs@^0.11.0": version "0.11.0" @@ -1032,16 +1072,16 @@ integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== "@types/express-serve-static-core@^4.17.33": - version "4.19.1" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.1.tgz#57d34698bb580720fd6e3c360d4b2fdef579b979" - integrity sha512-ej0phymbFLoCB26dbbq5PGScsf2JAJ4IJHjG10LalgUV36XKTmA4GdA+PVllKvRk0sEKt64X8975qFnkSi0hqA== + version "4.19.5" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz#218064e321126fcf9048d1ca25dd2465da55d9c6" + integrity sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg== dependencies: "@types/node" "*" "@types/qs" "*" "@types/range-parser" "*" "@types/send" "*" -"@types/express@^4.17.13": +"@types/express@*", "@types/express@^4.17.13": version "4.17.21" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== @@ -1123,6 +1163,11 @@ "@types/fined" "*" "@types/node" "*" +"@types/luxon@~3.4.0": + version "3.4.2" + resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-3.4.2.tgz#e4fc7214a420173cea47739c33cdf10874694db7" + integrity sha512-TifLZlFudklWlMBfhubvgqTXRzLDI5pCbGa4P8a3wPyUQSW+1xQ5eDsreP9DWHX3tjq1ke96uYG/nwundroWcA== + "@types/methods@^1.1.4": version "1.1.4" resolved "https://registry.yarnpkg.com/@types/methods/-/methods-1.1.4.tgz#d3b7ac30ac47c91054ea951ce9eed07b1051e547" @@ -1133,19 +1178,24 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== -"@types/node@*": - version "20.12.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.12.tgz#7cbecdf902085cec634fdb362172dfe12b8f2050" - integrity sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw== +"@types/multer@^1.4.11": + version "1.4.11" + resolved "https://registry.yarnpkg.com/@types/multer/-/multer-1.4.11.tgz#c70792670513b4af1159a2b60bf48cc932af55c5" + integrity sha512-svK240gr6LVWvv3YGyhLlA+6LRRWA4mnGIU7RcNmgjBYFl6665wcXrRfxGp5tEPVHUNm5FMcmq7too9bxCwX/w== + dependencies: + "@types/express" "*" + +"@types/node@*", "@types/node@^20.12.13": + version "20.14.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.10.tgz#a1a218290f1b6428682e3af044785e5874db469a" + integrity sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ== dependencies: undici-types "~5.26.4" -"@types/node@^20.12.13": - version "20.12.13" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.13.tgz#90ed3b8a4e52dd3c5dc5a42dde5b85b74ad8ed88" - integrity sha512-gBGeanV41c1L171rR7wjbMiEpEI/l5XFQdLLfhr/REwpgDy/4U8y89+i8kRiLzDyZdOkXh+cRaTetUnCYutoXA== - dependencies: - undici-types "~5.26.4" +"@types/node@^14.0.1": + version "14.18.63" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.63.tgz#1788fa8da838dbb5f9ea994b834278205db6ca2b" + integrity sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ== "@types/qs@*": version "6.9.15" @@ -1208,9 +1258,9 @@ "@types/node" "*" "@types/validator@^13.11.8": - version "13.11.10" - resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.11.10.tgz#feb364018cdd1f3d970a9e8c7f1c314c0a264fff" - integrity sha512-e2PNXoXLr6Z+dbfx5zSh9TRlXJrELycxiaXznp4S5+D2M3b9bqJEitNHA5923jhnB2zzFiZHa2f0SI1HoIahpg== + version "13.12.0" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.12.0.tgz#1fe4c3ae9de5cf5193ce64717c99ef2fa7d8756f" + integrity sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag== "@types/yargs-parser@*": version "21.0.3" @@ -1313,7 +1363,7 @@ resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== -"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.11.5": +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": version "1.12.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== @@ -1379,7 +1429,7 @@ resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== -"@webassemblyjs/wasm-edit@^1.11.5": +"@webassemblyjs/wasm-edit@^1.12.1": version "1.12.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b" integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== @@ -1414,7 +1464,7 @@ "@webassemblyjs/wasm-gen" "1.12.1" "@webassemblyjs/wasm-parser" "1.12.1" -"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.11.5": +"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1": version "1.12.1" resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937" integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== @@ -1464,11 +1514,6 @@ accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" -acorn-import-assertions@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== - acorn-import-attributes@^1.9.5: version "1.9.5" resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" @@ -1480,14 +1525,16 @@ acorn-jsx@^5.3.2: integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== acorn-walk@^8.1.1: - version "8.3.2" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" - integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== + version "8.3.3" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.3.tgz#9caeac29eefaa0c41e3d4c65137de4d6f34df43e" + integrity sha512-MxXdReSRhGO7VlFe1bRG/oI7/mdLV9B9JJT0N8vZOhF7gFRR5l3M8W9G8JxmKV+JC5mGqJ0QvqfSOLsCPa4nUw== + dependencies: + acorn "^8.11.0" -acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: - version "8.11.3" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" - integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== +acorn@^8.11.0, acorn@^8.4.1, acorn@^8.7.1, acorn@^8.8.2, acorn@^8.9.0: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== after-all-results@^2.0.0: version "2.0.0" @@ -1556,15 +1603,20 @@ ajv@^6.12.4, ajv@^6.12.5: uri-js "^4.2.2" ajv@^8.0.0: - version "8.14.0" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.14.0.tgz#f514ddfd4756abb200e1704414963620a625ebbb" - integrity sha512-oYs1UUtO97ZO2lJ4bwnWeQW8/zvOIQLGKcvPTsWmvc2SYgBb+upuNS5NxoLaMU4h8Ju3Nbj6Cq8mD2LQoqVKFA== + version "8.16.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.16.0.tgz#22e2a92b94f005f7e0f9c9d39652ef0b8f6f0cb4" + integrity sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw== dependencies: fast-deep-equal "^3.1.3" json-schema-traverse "^1.0.0" require-from-string "^2.0.2" uri-js "^4.4.1" +algebra.js@^0.2.6: + version "0.2.6" + resolved "https://registry.yarnpkg.com/algebra.js/-/algebra.js-0.2.6.tgz#23aacc86f8634e4c206d56c8077ef220a6221c74" + integrity sha512-ogfp7ukWOqIgBGERLLhArKhPORnmvn1t0pXvPvtfe15qlDV9BaDwCArHh5RyoEj2GHUqYeCWfBzQ7oZRqQxKDw== + ansi-colors@4.1.3: version "4.1.3" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" @@ -1639,6 +1691,51 @@ append-field@^1.0.0: resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" integrity sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ== +archiver-utils@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-2.1.0.tgz#e8a460e94b693c3e3da182a098ca6285ba9249e2" + integrity sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw== + dependencies: + glob "^7.1.4" + graceful-fs "^4.2.0" + lazystream "^1.0.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.union "^4.6.0" + normalize-path "^3.0.0" + readable-stream "^2.0.0" + +archiver-utils@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/archiver-utils/-/archiver-utils-3.0.4.tgz#a0d201f1cf8fce7af3b5a05aea0a337329e96ec7" + integrity sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw== + dependencies: + glob "^7.2.3" + graceful-fs "^4.2.0" + lazystream "^1.0.0" + lodash.defaults "^4.2.0" + lodash.difference "^4.5.0" + lodash.flatten "^4.4.0" + lodash.isplainobject "^4.0.6" + lodash.union "^4.6.0" + normalize-path "^3.0.0" + readable-stream "^3.6.0" + +archiver@^5.0.0: + version "5.3.2" + resolved "https://registry.yarnpkg.com/archiver/-/archiver-5.3.2.tgz#99991d5957e53bd0303a392979276ac4ddccf3b0" + integrity sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw== + dependencies: + archiver-utils "^2.1.0" + async "^3.2.4" + buffer-crc32 "^0.2.1" + readable-stream "^3.6.0" + readdir-glob "^1.1.2" + tar-stream "^2.2.0" + zip-stream "^4.1.0" + are-we-there-yet@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz#372e0e7bd279d8e94c653aaa1f67200884bf3e1c" @@ -1706,6 +1803,11 @@ async-value@^1.2.2: resolved "https://registry.yarnpkg.com/async-value/-/async-value-1.2.2.tgz#84517a1e7cb6b1a5b5e181fa31be10437b7fb125" integrity sha512-8rwtYe32OAS1W9CTwvknoyts+mc3ta8N7Pi0h7AjkMaKvsFbr39K+gEfZ7Z81aPXQ1sK5M23lgLy1QfZpcpadQ== +async@^3.2.4: + version "3.2.5" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" + integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -1716,6 +1818,13 @@ atomic-sleep@^1.0.0: resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== +axios@^0.26.0: + version "0.26.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" + integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== + dependencies: + follow-redirects "^1.14.8" + axios@^1.6.2: version "1.7.2" resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" @@ -1810,6 +1919,11 @@ bcrypt@^5.1.1: "@mapbox/node-pre-gyp" "^1.0.11" node-addon-api "^5.0.0" +big-integer@^1.6.17: + version "1.6.52" + resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.52.tgz#60a887f3047614a8e1bffe5d7173490a97dc8c85" + integrity sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg== + bignumber.js@^9.0.0: version "9.1.2" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.1.2.tgz#b7c4242259c008903b13707983b5f4bbd31eda0c" @@ -1825,7 +1939,15 @@ binary-search@^1.3.3: resolved "https://registry.yarnpkg.com/binary-search/-/binary-search-1.3.6.tgz#e32426016a0c5092f0f3598836a1c7da3560565c" integrity sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA== -bl@^4.1.0: +binary@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79" + integrity sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg== + dependencies: + buffers "~0.1.1" + chainsaw "~0.1.0" + +bl@^4.0.3, bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" integrity sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w== @@ -1834,6 +1956,11 @@ bl@^4.1.0: inherits "^2.0.4" readable-stream "^3.4.0" +bluebird@~3.4.1: + version "3.4.7" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3" + integrity sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA== + body-parser@1.20.2: version "1.20.2" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" @@ -1882,14 +2009,14 @@ breadth-filter@^2.0.0: object.entries "^1.0.4" browserslist@^4.21.10, browserslist@^4.22.2: - version "4.23.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.0.tgz#8f3acc2bbe73af7213399430890f86c63a5674ab" - integrity sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ== + version "4.23.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.2.tgz#244fe803641f1c19c28c48c4b6ec9736eb3d32ed" + integrity sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA== dependencies: - caniuse-lite "^1.0.30001587" - electron-to-chromium "^1.4.668" + caniuse-lite "^1.0.30001640" + electron-to-chromium "^1.4.820" node-releases "^2.0.14" - update-browserslist-db "^1.0.13" + update-browserslist-db "^1.1.0" bs-logger@0.x: version "0.2.6" @@ -1905,6 +2032,11 @@ bser@2.1.1: dependencies: node-int64 "^0.4.0" +buffer-crc32@^0.2.1, buffer-crc32@^0.2.13: + version "0.2.13" + resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" + integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== + buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" @@ -1915,6 +2047,11 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== +buffer-indexof-polyfill@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.2.tgz#d2732135c5999c64b277fcf9b1abe3498254729c" + integrity sha512-I7wzHwA3t1/lwXQh+A5PbNvJxgfo5r3xulgpYDB5zckTu/Z9oUK9biouBKQUjEqzaz3HnAT6TYoovmE+GqSf7A== + buffer@^5.5.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -1931,6 +2068,11 @@ buffer@^6.0.3: base64-js "^1.3.1" ieee754 "^1.2.1" +buffers@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" + integrity sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ== + busboy@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" @@ -1977,10 +2119,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001587: - version "1.0.30001621" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001621.tgz#4adcb443c8b9c8303e04498318f987616b8fea2e" - integrity sha512-+NLXZiviFFKX0fk8Piwv3PfLPGtRqJeq2TiNoUff/qB5KJgwecJTvCXDpmlyP/eCI/GUEmp/h/y5j0yckiiZrA== +caniuse-lite@^1.0.30001640: + version "1.0.30001641" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001641.tgz#3572862cd18befae3f637f2a1101cc033c6782ac" + integrity sha512-Phv5thgl67bHYo1TtMY/MurjkHhV4EDaCosezRXgZ8jzA/Ub+wjxAvbGvjoFENStinwi5kCyOYV3mi5tOGykwA== capital-case@^1.0.4: version "1.0.4" @@ -1991,6 +2133,13 @@ capital-case@^1.0.4: tslib "^2.0.3" upper-case-first "^2.0.2" +chainsaw@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98" + integrity sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ== + dependencies: + traverse ">=0.3.0 <0.4" + chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" @@ -2070,9 +2219,9 @@ chownr@^2.0.0: integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ== chrome-trace-event@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac" - integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg== + version "1.0.4" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== ci-info@^3.2.0: version "3.9.0" @@ -2136,10 +2285,10 @@ cli-spinners@^2.2.0, cli-spinners@^2.5.0, cli-spinners@^2.9.2: resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.9.2.tgz#1773a8f4b9c4d6ac31563df53b3fc1d79462fe41" integrity sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg== -cli-table3@0.6.3: - version "0.6.3" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.3.tgz#61ab765aac156b52f222954ffc607a6f01dbeeb2" - integrity sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg== +cli-table3@0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.6.5.tgz#013b91351762739c16a9567c21a04632e449bf2f" + integrity sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ== dependencies: string-width "^4.2.0" optionalDependencies: @@ -2254,11 +2403,26 @@ comment-json@4.2.3: has-own-prop "^2.0.0" repeat-string "^1.6.1" +complex.js@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.1.1.tgz#0675dac8e464ec431fb2ab7d30f41d889fb25c31" + integrity sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg== + component-emitter@^1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.1.tgz#ef1d5796f7d93f135ee6fb684340b26403c97d17" integrity sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ== +compress-commons@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/compress-commons/-/compress-commons-4.1.2.tgz#6542e59cb63e1f46a8b21b0e06f9a32e4c8b06df" + integrity sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg== + dependencies: + buffer-crc32 "^0.2.13" + crc32-stream "^4.0.2" + normalize-path "^3.0.0" + readable-stream "^3.6.0" + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -2353,6 +2517,19 @@ cosmiconfig@^8.2.0: parse-json "^5.2.0" path-type "^4.0.0" +crc-32@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff" + integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ== + +crc32-stream@^4.0.2: + version "4.0.3" + resolved "https://registry.yarnpkg.com/crc32-stream/-/crc32-stream-4.0.3.tgz#85dd677eb78fa7cad1ba17cc506a597d41fc6f33" + integrity sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw== + dependencies: + crc-32 "^1.2.0" + readable-stream "^3.4.0" + create-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" @@ -2371,6 +2548,14 @@ create-require@^1.1.0: resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== +cron@3.1.7: + version "3.1.7" + resolved "https://registry.yarnpkg.com/cron/-/cron-3.1.7.tgz#3423d618ba625e78458fff8cb67001672d49ba0d" + integrity sha512-tlBg7ARsAMQLzgwqVxy8AZl/qlTc5nibqYwtNGoCrd+cV+ugI+tvZC1oT/8dFH8W455YrywGykx/KMmAqOr7Jw== + dependencies: + "@types/luxon" "~3.4.0" + luxon "~3.4.0" + cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -2380,7 +2565,7 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -dayjs@^1.11.9: +dayjs@^1.11.9, dayjs@^1.8.34: version "1.11.11" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.11.tgz#dfe0e9d54c5f8b68ccf8ca5f72ac603e7e5ed59e" integrity sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg== @@ -2392,25 +2577,23 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4: +debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: version "4.3.5" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== dependencies: ms "2.1.2" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA== +decimal.js@^10.4.3: + version "10.4.3" + resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" + integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== + dedent@^1.0.0: version "1.5.3" resolved "https://registry.yarnpkg.com/dedent/-/dedent-1.5.3.tgz#99aee19eb9bae55a67327717b6e848d0bf777e5a" @@ -2550,6 +2733,13 @@ dotenv@16.4.5, dotenv@^16.0.3, dotenv@^16.4.5: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== +duplexer2@~0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + integrity sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA== + dependencies: + readable-stream "^2.0.2" + eastasianwidth@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" @@ -2568,9 +2758,9 @@ ee-first@1.1.1: integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== elastic-apm-node@^4.5.4: - version "4.5.4" - resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-4.5.4.tgz#add7c5a53f8a4ec29989e3365c9f2053ec64a20d" - integrity sha512-PqX8a5PdWo+mtH1Vn+xjmhJpa2RE9zbKDKFkKoENKG118KVgukxhFlVIpb3qrht9aeRkPxHqQsPNtNV3ljPjew== + version "4.7.0" + resolved "https://registry.yarnpkg.com/elastic-apm-node/-/elastic-apm-node-4.7.0.tgz#27d7d78f659dbed6e03a5c4d50b5bab62b775bc2" + integrity sha512-fOcDYOcZ/180Ib0p8xLNF8iDu9e1QGqZiEizOntdh1GmkurN3sIGVgGlE25s9LRBUMmD2bMWwlER56T8moW+tg== dependencies: "@elastic/ecs-pino-format" "^1.5.0" "@opentelemetry/api" "^1.4.1" @@ -2590,7 +2780,7 @@ elastic-apm-node@^4.5.4: fast-safe-stringify "^2.0.7" fast-stream-to-buffer "^1.0.0" http-headers "^3.0.2" - import-in-the-middle "1.7.4" + import-in-the-middle "1.8.0" json-bigint "^1.0.0" lru-cache "^10.0.1" measured-reporting "^1.51.1" @@ -2610,10 +2800,10 @@ elastic-apm-node@^4.5.4: stream-chopper "^3.0.1" unicode-byte-truncate "^1.0.0" -electron-to-chromium@^1.4.668: - version "1.4.783" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.783.tgz#933887165b8b6025a81663d2d97cf4b85cde27b2" - integrity sha512-bT0jEz/Xz1fahQpbZ1D7LgmPYZ3iHVY39NcWWro1+hA2IvjiPeaXtfSqrQ+nXjApMvQRE2ASt1itSLRrebHMRQ== +electron-to-chromium@^1.4.820: + version "1.4.823" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.823.tgz#38587f7aa55bed14930f04091dfc65c39a3d8bd7" + integrity sha512-4h+oPeAiGQOHFyUJOqpoEcPj/xxlicxBzOErVeYVMMmAiXUXsGpsFd0QXBMaUUbnD8hhSfLf9uw+MlsoIA7j5w== emittery@^0.13.1: version "0.13.1" @@ -2647,10 +2837,10 @@ end-of-stream@^1.4.1, end-of-stream@^1.4.4: dependencies: once "^1.4.0" -enhanced-resolve@^5.0.0, enhanced-resolve@^5.15.0, enhanced-resolve@^5.7.0: - version "5.16.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz#e8bc63d51b826d6f1cbc0a150ecb5a8b0c62e567" - integrity sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw== +enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.0, enhanced-resolve@^5.7.0: + version "5.17.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz#d037603789dd9555b89aaec7eb78845c49089bc5" + integrity sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -2687,9 +2877,9 @@ es-errors@^1.3.0: integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== es-module-lexer@^1.2.1: - version "1.5.3" - resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.3.tgz#25969419de9c0b1fbe54279789023e8a9a788412" - integrity sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg== + version "1.5.4" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" + integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== es-object-atoms@^1.0.0: version "1.0.0" @@ -2708,7 +2898,12 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== -escape-string-regexp@5.0.0, escape-string-regexp@^5.0.0: +escape-latex@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.2.0.tgz#07c03818cf7dac250cce517f4fda1b001ef2bca1" + integrity sha512-nV5aVWW1K0wEiUIEdZ4erkGGH8mDxGyxSeqPzRNtWP7ataw+/olFObw7hujFWlVjNsaDFw5VZ5NzVSIqRgfTiw== + +escape-string-regexp@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8" integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw== @@ -2820,9 +3015,9 @@ esprima@^4.0.0, esprima@^4.0.1: integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esquery@^1.4.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== dependencies: estraverse "^5.1.0" @@ -2863,6 +3058,21 @@ events@^3.2.0, events@^3.3.0: resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== +exceljs@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/exceljs/-/exceljs-4.4.0.tgz#cfb1cb8dcc82c760a9fc9faa9e52dadab66b0156" + integrity sha512-XctvKaEMaj1Ii9oDOqbW/6e1gXknSY4g/aLCDicOXqBE4M0nRWkUu0PTp++UPNzoFY12BNHMfs/VadKIS6llvg== + dependencies: + archiver "^5.0.0" + dayjs "^1.8.34" + fast-csv "^4.3.1" + jszip "^3.10.1" + readable-stream "^3.6.0" + saxes "^5.0.1" + tmp "^0.2.0" + unzipper "^0.10.11" + uuid "^8.3.0" + execa@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" @@ -2957,6 +3167,14 @@ faker@4.1.0: resolved "https://registry.yarnpkg.com/faker/-/faker-4.1.0.tgz#1e45bbbecc6774b3c195fad2835109c6d748cc3f" integrity sha512-ILKg69P6y/D8/wSmDXw35Ly0re8QzQ8pMfBCflsGiZG2ZjMUNLYNexA6lz5pkmJlepVdsiDFUxYAzPQ9/+iGLA== +fast-csv@^4.3.1: + version "4.3.6" + resolved "https://registry.yarnpkg.com/fast-csv/-/fast-csv-4.3.6.tgz#70349bdd8fe4d66b1130d8c91820b64a21bc4a63" + integrity sha512-2RNSpuwwsJGP0frGsOmTb9oUF+VkFSM4SyLTDgwf2ciHWTarN0lQTC+F2f/t5J9QjW+c65VFIAAu85GsvMIusw== + dependencies: + "@fast-csv/format" "4.3.5" + "@fast-csv/parse" "4.3.6" + fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" @@ -3019,21 +3237,13 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" -figures@^3.0.0: +figures@^3.0.0, figures@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== dependencies: escape-string-regexp "^1.0.5" -figures@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-5.0.0.tgz#126cd055052dea699f8a54e8c9450e6ecfc44d5f" - integrity sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg== - dependencies: - escape-string-regexp "^5.0.0" - is-unicode-supported "^1.2.0" - file-entry-cache@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" @@ -3117,7 +3327,7 @@ flatted@^3.2.9: resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== -follow-redirects@^1.15.6: +follow-redirects@^1.14.8, follow-redirects@^1.15.6: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== @@ -3135,9 +3345,9 @@ for-own@^1.0.0: for-in "^1.0.1" foreground-child@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" - integrity sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg== + version "3.2.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.2.1.tgz#767004ccf3a5b30df39bed90718bab43fe0a59f7" + integrity sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA== dependencies: cross-spawn "^7.0.0" signal-exit "^4.0.1" @@ -3189,11 +3399,21 @@ forwarded@0.2.0: resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== +fraction.js@^4.3.7: + version "4.3.7" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" + integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew== + fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== +fs-constants@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" + integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== + fs-extra@^10.0.0: version "10.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" @@ -3203,6 +3423,15 @@ fs-extra@^10.0.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^11.2.0: + version "11.2.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.2.0.tgz#e70e17dfad64232287d01929399e0ea7c86b0e5b" + integrity sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -3225,6 +3454,16 @@ fsevents@^2.3.2, fsevents@~2.3.2: resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== +fstream@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" @@ -3246,15 +3485,15 @@ gauge@^3.0.0: wide-align "^1.1.2" gaxios@^6.0.0, gaxios@^6.0.3, gaxios@^6.1.1: - version "6.6.0" - resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-6.6.0.tgz#af8242fff0bbb82a682840d5feaa91b6a1c58be4" - integrity sha512-bpOZVQV5gthH/jVCSuYuokRo2bTKOcuBiVWpjmTn6C5Agl5zclGfTljuGsQZxwwDBkli+YhZhP4TdlqTnhOezQ== + version "6.7.0" + resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-6.7.0.tgz#37b7c5961cb67d8d4b0ae8110dcd83cc6791eb6d" + integrity sha512-DSrkyMTfAnAm4ks9Go20QGOcXEyW/NmZhvTYBU2rb4afBB393WIMQPWPEDMl/k8xqiNN9HYq2zao3oWXsdl2Tg== dependencies: extend "^3.0.2" https-proxy-agent "^7.0.1" is-stream "^2.0.0" node-fetch "^2.6.9" - uuid "^9.0.1" + uuid "^10.0.0" gcp-metadata@^6.1.0: version "6.1.0" @@ -3319,16 +3558,17 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@10.3.10: - version "10.3.10" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.3.10.tgz#0351ebb809fd187fe421ab96af83d3a70715df4b" - integrity sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g== +glob@10.4.2: + version "10.4.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.2.tgz#bed6b95dade5c1f80b4434daced233aee76160e5" + integrity sha512-GwMlUF6PkPo3Gk21UxkCohOv0PLcIXVtKyLlpEI28R/cO/4eNOdmLk3CMW1wROV/WR/EsZOWAfBbBOqYvs88/w== dependencies: foreground-child "^3.1.0" - jackspeak "^2.3.5" - minimatch "^9.0.1" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - path-scurry "^1.10.1" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" glob@7.1.6: version "7.1.6" @@ -3343,17 +3583,18 @@ glob@7.1.6: path-is-absolute "^1.0.0" glob@^10.3.10: - version "10.4.1" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.1.tgz#0cfb01ab6a6b438177bfe6a58e2576f6efe909c2" - integrity sha512-2jelhlq3E4ho74ZyVLN03oKdAZVUa6UDZzFLVH1H7dnoax+y9qyaq8zBkfDIggjniU19z0wU18y16jMB2eyVIw== + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== dependencies: foreground-child "^3.1.0" jackspeak "^3.1.2" minimatch "^9.0.4" minipass "^7.1.2" + package-json-from-dist "^1.0.0" path-scurry "^1.11.1" -glob@^7.0.0, glob@^7.1.3, glob@^7.1.4: +glob@^7.1.3, glob@^7.1.4, glob@^7.2.3: version "7.2.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q== @@ -3365,16 +3606,6 @@ glob@^7.0.0, glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^9.2.0: - version "9.3.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.5.tgz#ca2ed8ca452781a3009685607fdf025a899dfe21" - integrity sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q== - dependencies: - fs.realpath "^1.0.0" - minimatch "^8.0.2" - minipass "^4.2.4" - path-scurry "^1.6.1" - global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" @@ -3455,9 +3686,9 @@ googleapis-common@^7.0.0: uuid "^9.0.0" googleapis@^140.0.0: - version "140.0.0" - resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-140.0.0.tgz#907d2996e2ab389eacacc94e706e8a6f3700afd2" - integrity sha512-r8i++0lnexrvRA0/uogz3N3eJprddjxAcueTO5f09D/U5yxaOm5G+a892QkHsV+o15NP9whlLUiJr9zazb9ePg== + version "140.0.1" + resolved "https://registry.yarnpkg.com/googleapis/-/googleapis-140.0.1.tgz#fe347353761f21a2959777186026475fad08ee0f" + integrity sha512-ZGvBX4mQcFXO9ACnVNg6Aqy3KtBPB5zTuue43YVLxwn8HSv8jB7w+uDKoIPSoWuxGROgnj2kbng6acXncOQRNA== dependencies: google-auth-library "^9.0.0" googleapis-common "^7.0.0" @@ -3469,7 +3700,7 @@ gopd@^1.0.1: dependencies: get-intrinsic "^1.1.3" -graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.4, graceful-fs@^4.2.9: +graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.10, graceful-fs@^4.2.11, graceful-fs@^4.2.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -3536,7 +3767,7 @@ has-unicode@^2.0.1: resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== -hasown@^2.0.0: +hasown@^2.0.0, hasown@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== @@ -3600,9 +3831,9 @@ https-proxy-agent@^5.0.0: debug "4" https-proxy-agent@^7.0.1: - version "7.0.4" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz#8e97b841a029ad8ddc8731f26595bad868cb4168" - integrity sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg== + version "7.0.5" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz#9e8b5013873299e11fab6fd548405da2d6c602b2" + integrity sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw== dependencies: agent-base "^7.0.2" debug "4" @@ -3636,6 +3867,11 @@ ignore@^5.2.0, ignore@^5.2.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ== + import-fresh@^3.2.1, import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" @@ -3644,10 +3880,10 @@ import-fresh@^3.2.1, import-fresh@^3.3.0: parent-module "^1.0.0" resolve-from "^4.0.0" -import-in-the-middle@1.7.4: - version "1.7.4" - resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.7.4.tgz#508da6e91cfa84f210dcdb6c0a91ab0c9e8b3ebc" - integrity sha512-Lk+qzWmiQuRPPulGQeK5qq0v32k2bHnWrRPFgqyvhw7Kkov5L6MOLOIU3pcWeujc9W4q54Cp3Q2WV16eQkc7Bg== +import-in-the-middle@1.8.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.8.0.tgz#c94d88d53701de9a248f9710b41f533e67f598a4" + integrity sha512-/xQjze8szLNnJ5rvHSzn+dcVXqCAU6Plbk4P24U/jwPmg1wy7IIp9OjKIO5tYue8GSPhDpPDiApQjvBUmWwhsQ== dependencies: acorn "^8.8.2" acorn-import-attributes "^1.9.5" @@ -3680,7 +3916,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -3711,18 +3947,18 @@ inquirer@8.2.6: through "^2.3.6" wrap-ansi "^6.0.1" -inquirer@9.2.12: - version "9.2.12" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-9.2.12.tgz#0348e9311765b7c93fce143bb1c0ef1ae879b1d7" - integrity sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q== +inquirer@9.2.15: + version "9.2.15" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-9.2.15.tgz#2135a36190a6e5c92f5d205e0af1fea36b9d3492" + integrity sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg== dependencies: - "@ljharb/through" "^2.3.11" + "@ljharb/through" "^2.3.12" ansi-escapes "^4.3.2" chalk "^5.3.0" cli-cursor "^3.1.0" cli-width "^4.1.0" external-editor "^3.1.0" - figures "^5.0.0" + figures "^3.2.0" lodash "^4.17.21" mute-stream "1.0.0" ora "^5.4.1" @@ -3733,18 +3969,14 @@ inquirer@9.2.12: wrap-ansi "^6.2.0" inquirer@^9.2.10: - version "9.2.22" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-9.2.22.tgz#718cb4153f0d35176aab27d495f8e358d1e2008f" - integrity sha512-SqLLa/Oe5rZUagTR9z+Zd6izyatHglbmbvVofo1KzuVB54YHleWzeHNLoR7FOICGOeQSqeLh1cordb3MzhGcEw== + version "9.3.5" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-9.3.5.tgz#e6887191786d2cda8a44d50119ac0eb0a1a983be" + integrity sha512-SVRCRovA7KaT6nqWB2mCNpTvU4cuZ0hOXo5KPyiyOcNNUIZwq/JKtvXuDJNaxfuJKabBYRu1ecHze0YEwDYoRQ== dependencies: - "@inquirer/figures" "^1.0.2" - "@ljharb/through" "^2.3.13" + "@inquirer/figures" "^1.0.3" ansi-escapes "^4.3.2" - chalk "^5.3.0" - cli-cursor "^3.1.0" cli-width "^4.1.0" external-editor "^3.1.0" - lodash "^4.17.21" mute-stream "1.0.0" ora "^5.4.1" run-async "^3.0.0" @@ -3752,11 +3984,7 @@ inquirer@^9.2.10: string-width "^4.2.3" strip-ansi "^6.0.1" wrap-ansi "^6.2.0" - -interpret@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" - integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + yoctocolors-cjs "^2.1.2" interpret@^3.1.1: version "3.1.1" @@ -3789,11 +4017,11 @@ is-binary-path@~2.1.0: binary-extensions "^2.0.0" is-core-module@^2.13.0: - version "2.13.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" - integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + version "2.14.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.14.0.tgz#43b8ef9f46a6a08888db67b1ffd4ec9e3dfd59d1" + integrity sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A== dependencies: - hasown "^2.0.0" + hasown "^2.0.2" is-extglob@^2.1.1: version "2.1.1" @@ -3888,7 +4116,7 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -is-unicode-supported@^1.2.0, is-unicode-supported@^1.3.0: +is-unicode-supported@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz#d824984b616c292a2e198207d4a609983842f714" integrity sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ== @@ -3940,9 +4168,9 @@ istanbul-lib-instrument@^5.0.4: semver "^6.3.0" istanbul-lib-instrument@^6.0.0: - version "6.0.2" - resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.2.tgz#91655936cf7380e4e473383081e38478b69993b1" - integrity sha512-1WUsZ9R1lA0HtBSohTkm39WTPlNKSJ5iFk7UwqXkBLoHQT+hfqPsfsTDVuZdKGaBwn7din9bS7SsnoAr943hvw== + version "6.0.3" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz#fa15401df6c15874bcb2105f773325d78c666765" + integrity sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q== dependencies: "@babel/core" "^7.23.9" "@babel/parser" "^7.23.9" @@ -3981,23 +4209,19 @@ iterare@1.2.1: resolved "https://registry.yarnpkg.com/iterare/-/iterare-1.2.1.tgz#139c400ff7363690e33abffa33cbba8920f00042" integrity sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q== -jackspeak@^2.3.5: - version "2.3.6" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-2.3.6.tgz#647ecc472238aee4b06ac0e461acc21a8c505ca8" - integrity sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ== +jackspeak@^3.1.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.2.tgz#c3d1e00071d52dba8b0dac17cd2a12d0187d2989" + integrity sha512-qH3nOSj8q/8+Eg8LUPOq3C+6HWkpUioIjDsq1+D4zY91oZvpPttw8GwtF1nReRYKXl+1AORyFqtm2f5Q1SB6/Q== dependencies: "@isaacs/cliui" "^8.0.2" optionalDependencies: "@pkgjs/parseargs" "^0.11.0" -jackspeak@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.1.2.tgz#eada67ea949c6b71de50f1b09c92a961897b90ab" - integrity sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" +javascript-natural-sort@^0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" + integrity sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw== jest-changed-files@^29.7.0: version "29.7.0" @@ -4428,16 +4652,16 @@ json5@^2.2.1, json5@^2.2.2, json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== -jsonc-parser@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" - integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== - jsonc-parser@3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.1.tgz#031904571ccf929d7670ee8c547545081cb37f1a" integrity sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA== +jsonc-parser@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4" + integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ== + jsonfile@^6.0.1: version "6.1.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae" @@ -4463,6 +4687,16 @@ jsonwebtoken@9.0.2: ms "^2.1.1" semver "^7.5.4" +jszip@^3.10.1: + version "3.10.1" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.10.1.tgz#34aee70eb18ea1faec2f589208a157d1feb091c2" + integrity sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + setimmediate "^1.0.5" + jwa@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" @@ -4514,6 +4748,13 @@ kleur@^3.0.3: resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== +lazystream@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/lazystream/-/lazystream-1.0.1.tgz#494c831062f1f9408251ec44db1cba29242a2638" + integrity sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw== + dependencies: + readable-stream "^2.0.5" + leven@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2" @@ -4528,9 +4769,16 @@ levn@^0.4.1: type-check "~0.4.0" libphonenumber-js@^1.10.53: - version "1.11.2" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.2.tgz#9ddd7d1a1e1be0e7c596c7e09487c362b4f1210c" - integrity sha512-V9mGLlaXN1WETzqQvSu6qf6XVAr3nFuJvWsHcuzCCCo6xUKawwSxOPTpan5CGOSKTn5w/bQuCZcLPJkyysgC3w== + version "1.11.4" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.4.tgz#e63fe553f45661b30bb10bb8c82c9cf2b22ec32a" + integrity sha512-F/R50HQuWWYcmU/esP5jrH5LiWYaN7DpN0a/99U8+mnGGtnx8kmRE+649dQh3v+CowXXZc8vpkf5AmYkO0AQ7Q== + +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" liftoff@^4.0.0: version "4.0.0" @@ -4551,6 +4799,11 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg== +listenercount@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937" + integrity sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ== + loader-runner@^4.2.0: version "4.3.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" @@ -4570,11 +4823,36 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== + +lodash.difference@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.difference/-/lodash.difference-4.5.0.tgz#9ccb4e505d486b91651345772885a2df27fd017c" + integrity sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA== + +lodash.escaperegexp@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" + integrity sha512-TM9YBvyC84ZxE3rgfefxUWiQKLilstD6k7PTGt6wfbtXF8ixIJLOL3VYyV/z+ZiPLsVxAsKAFVwWlWeb2Y8Yyw== + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g== + lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ== +lodash.groupby@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.groupby/-/lodash.groupby-4.6.0.tgz#0b08a1dcf68397c397855c3239783832df7403d1" + integrity sha512-5dcWxm23+VAoz+awKmBaiBvzox8+RqMgFhi7UvX9DHZr2HdxHXM/Wrf8cfKpsW37RNrvtPn6hSwNqurSILbmJw== + lodash.includes@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" @@ -4585,11 +4863,26 @@ lodash.isboolean@^3.0.3: resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== +lodash.isequal@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" + integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== + +lodash.isfunction@^3.0.9: + version "3.0.9" + resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" + integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== + lodash.isinteger@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== +lodash.isnil@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/lodash.isnil/-/lodash.isnil-4.0.0.tgz#49e28cd559013458c814c5479d3c663a21bfaa6c" + integrity sha512-up2Mzq3545mwVnMhTDMdfoG1OurpA/s5t88JmQX809eH3C8491iu2sfKhTfhQtKY78oPNhiaHJUpT/dUDAAtng== + lodash.isnumber@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" @@ -4605,6 +4898,11 @@ lodash.isstring@^4.0.1: resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== +lodash.isundefined@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz#23ef3d9535565203a66cefd5b830f848911afb48" + integrity sha512-MXB1is3s899/cD8jheYYE2V9qTHwKvt+npCwpD+1Sxm3Q3cECXCiYHjeHWXNwr6Q0SOBPrYUDxendrO6goVTEA== + lodash.memoize@4.x: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -4625,6 +4923,16 @@ lodash.sortby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438" integrity sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA== +lodash.union@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" + integrity sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw== + +lodash.uniq@^4.5.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" + integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== + lodash@4.17.21, lodash@^4.17.21: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -4661,9 +4969,9 @@ lower-case@^2.0.2: tslib "^2.0.3" lru-cache@^10.0.1, lru-cache@^10.2.0: - version "10.2.2" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.2.tgz#48206bc114c1252940c41b25b41af5b545aca878" - integrity sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ== + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== lru-cache@^5.1.1: version "5.1.1" @@ -4672,10 +4980,15 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" -magic-string@0.30.5: - version "0.30.5" - resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.5.tgz#1994d980bd1c8835dc6e78db7cbd4ae4f24746f9" - integrity sha512-7xlpfBaQaP/T6Vh8MO/EqXSW5En6INHEvEXQiuff7Gku0PWjU3uf6w/j9o7O+SpB5fOAkrI5HeoNgwjEO0pFsA== +luxon@~3.4.0: + version "3.4.4" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.4.tgz#cf20dc27dc532ba41a169c43fdcc0063601577af" + integrity sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA== + +magic-string@0.30.8: + version "0.30.8" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.8.tgz#14e8624246d2bedba70d5462aa99ac9681844613" + integrity sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ== dependencies: "@jridgewell/sourcemap-codec" "^1.4.15" @@ -4722,6 +5035,21 @@ mapcap@^1.0.0: resolved "https://registry.yarnpkg.com/mapcap/-/mapcap-1.0.0.tgz#e8e29d04a160eaf8c92ec4bcbd2c5d07ed037e5a" integrity sha512-KcNlZSlFPx+r1jYZmxEbTVymG+dIctf10WmWkuhrhrblM+KMoF77HelwihL5cxYlORye79KoR4IlOOk99lUJ0g== +mathjs@^13.0.2: + version "13.0.2" + resolved "https://registry.yarnpkg.com/mathjs/-/mathjs-13.0.2.tgz#eb87e31a21d9ffc57e26ce98ddb14a4a07b13d91" + integrity sha512-8vK/+InU4FTphRTWsrnvRsgSjbyNupRQYDDIXLuEGDZtJsGdbA9dVV4HZ0amBQb+RXplRjVJNGZZfB0WoHWFWA== + dependencies: + "@babel/runtime" "^7.24.7" + complex.js "^2.1.1" + decimal.js "^10.4.3" + escape-latex "^1.2.0" + fraction.js "^4.3.7" + javascript-natural-sort "^0.7.1" + seedrandom "^3.0.5" + tiny-emitter "^2.1.0" + typed-function "^4.2.1" + measured-core@^1.51.1: version "1.51.1" resolved "https://registry.yarnpkg.com/measured-core/-/measured-core-1.51.1.tgz#98989705c00bfb0d8a20e665a9f8d6e246a40518" @@ -4780,6 +5108,14 @@ micromatch@^4.0.0, micromatch@^4.0.4: braces "^3.0.3" picomatch "^2.3.1" +midtrans-client@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/midtrans-client/-/midtrans-client-1.3.1.tgz#2cec3cfbbea35529c99683d3cee7fe21e5e3aa25" + integrity sha512-GoT2j9m0yHct9aBGeHLTYW9KWyaqep29gzbky8npffyMJfld/Y8OD9eBGM2hT4QNhe1M2e4XqyK/iyjU+KJMYA== + dependencies: + axios "^0.26.0" + lodash "^4.17.21" + mime-db@1.52.0: version "1.52.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" @@ -4814,17 +5150,17 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@^8.0.2: - version "8.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.4.tgz#847c1b25c014d4e9a7f68aaf63dedd668a626229" - integrity sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA== +minimatch@^5.1.0: + version "5.1.6" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" + integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.1, minimatch@^9.0.4: - version "9.0.4" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" - integrity sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw== +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== dependencies: brace-expansion "^2.0.1" @@ -4840,11 +5176,6 @@ minipass@^3.0.0: dependencies: yallist "^4.0.0" -minipass@^4.2.4: - version "4.2.8" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" - integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== - minipass@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" @@ -4863,7 +5194,7 @@ minizlib@^2.1.1: minipass "^3.0.0" yallist "^4.0.0" -mkdirp@^0.5.4: +"mkdirp@>=0.5 0", mkdirp@^0.5.4: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw== @@ -4890,6 +5221,11 @@ module-details-from-path@^1.0.3: resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A== +moment@^2.30.1: + version "2.30.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" + integrity sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how== + monitor-event-loop-delay@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/monitor-event-loop-delay/-/monitor-event-loop-delay-1.0.0.tgz#b5ab78165a3bb93f2b275c50d01430c7f155d1f7" @@ -5037,6 +5373,11 @@ node-releases@^2.0.14: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== +nodemailer@^6.9.14: + version "6.9.14" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.14.tgz#845fda981f9fd5ac264f4446af908a7c78027f75" + integrity sha512-Dobp/ebDKBvz91sbtRKhcznLThrKxKt97GI2FAlAyy+fk19j73Uz3sBXolVtmcXjaorivqsbbbjDY+Jkt4/bQA== + nopt@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" @@ -5084,9 +5425,9 @@ object-identity-map@^1.0.2: object.entries "^1.1.0" object-inspect@^1.13.1: - version "1.13.1" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" - integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== object-keys@^1.1.1: version "1.1.1" @@ -5266,6 +5607,16 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-json-from-dist@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" + integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== + +pako@~1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + param-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5" @@ -5375,7 +5726,7 @@ path-root@^0.1.1: dependencies: path-root-regex "^0.1.0" -path-scurry@^1.10.1, path-scurry@^1.11.1, path-scurry@^1.6.1: +path-scurry@^1.11.1: version "1.11.1" resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== @@ -5435,9 +5786,9 @@ pg-types@^2.1.0: postgres-interval "^1.1.0" pg@^8.11.5: - version "8.11.5" - resolved "https://registry.yarnpkg.com/pg/-/pg-8.11.5.tgz#e722b0a5f1ed92931c31758ebec3ddf878dd4128" - integrity sha512-jqgNHSKL5cbDjFlHyYsCXmQDrfIX/3RsNwYqpd4N0Kt8niLuNoRNH+aazv6cOd43gPh9Y4DjQCtb+X0MH0Hvnw== + version "8.12.0" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.12.0.tgz#9341724db571022490b657908f65aee8db91df79" + integrity sha512-A+LHUSnwnxrnL/tZ+OLfqR1SxLN3c/pgDztZ47Rpbsd4jUytsTtwQo/TLPRzPJMp/1pbhYVhH9cuSZLAajNfjQ== dependencies: pg-connection-string "^2.6.4" pg-pool "^3.6.2" @@ -5459,10 +5810,10 @@ picocolors@^1.0.0, picocolors@^1.0.1: resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== -picomatch@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-3.0.1.tgz#817033161def55ec9638567a2f3bbc876b3e7516" - integrity sha512-I3EurrIQMlRc9IaAZnqRR044Phh2DXY+55o7uJ0V+hYZAcQYSuFWsc9q5PvyDHUSCe1Qxn/iBz+78s86zWnGag== +picomatch@4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.1.tgz#68c26c8837399e5819edce48590412ea07f17a07" + integrity sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.1: version "2.3.1" @@ -5632,9 +5983,9 @@ qs@6.11.0: side-channel "^1.0.4" qs@^6.11.0, qs@^6.7.0: - version "6.12.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.1.tgz#39422111ca7cbdb70425541cba20c7d7b216599a" - integrity sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ== + version "6.12.3" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.3.tgz#e43ce03c8521b9c7fd7f1f13e514e5ca37727754" + integrity sha512-AWJm14H1vVaO/iNZ4/hO+HyaTehuy9nRqVdkTqlJt0HWvBiBIEXFmb4C0DGeYo3Xes9rrEW+TxHsaigCbN5ICQ== dependencies: side-channel "^1.0.6" @@ -5675,7 +6026,7 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== -readable-stream@^2.2.2: +readable-stream@^2.0.0, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.2.2, readable-stream@~2.3.6: version "2.3.8" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.8.tgz#91125e8042bba1b9887f49345f6277027ce8be9b" integrity sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA== @@ -5688,7 +6039,7 @@ readable-stream@^2.2.2: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.0.6, readable-stream@^3.4.0, readable-stream@^3.6.0, readable-stream@^3.6.2: +readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0, readable-stream@^3.6.2: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== @@ -5708,6 +6059,13 @@ readable-stream@^4.0.0: process "^0.11.10" string_decoder "^1.3.0" +readdir-glob@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/readdir-glob/-/readdir-glob-1.1.3.tgz#c3d831f51f5e7bfa62fa2ffbe4b508c640f09584" + integrity sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA== + dependencies: + minimatch "^5.1.0" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -5720,13 +6078,6 @@ real-require@^0.2.0: resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== -rechoir@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" - integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== - dependencies: - resolve "^1.1.6" - rechoir@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.8.0.tgz#49f866e0d32146142da3ad8f0eff352b3215ff22" @@ -5744,6 +6095,11 @@ reflect-metadata@^0.2.0, reflect-metadata@^0.2.1: resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b" integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q== +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + relative-microtime@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/relative-microtime/-/relative-microtime-2.0.0.tgz#cceed2af095ecd72ea32011279c79e5fcc7de29b" @@ -5808,7 +6164,7 @@ resolve.exports@^2.0.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== -resolve@^1.1.6, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.22.4: +resolve@^1.20.0, resolve@^1.22.1, resolve@^1.22.4: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -5838,12 +6194,12 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== -rimraf@4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-4.4.1.tgz#bd33364f67021c5b79e93d7f4fa0568c7c21b755" - integrity sha512-Gk8NlF062+T9CqNGn6h4tls3k6T1+/nXdOcSZVikNVtlRdYpA7wRJJMoXmuvOnLW844rPjdQ7JgXCYM6PPC/og== +rimraf@2: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== dependencies: - glob "^9.2.0" + glob "^7.1.3" rimraf@^3.0.2: version "3.0.2" @@ -5896,6 +6252,13 @@ safe-stable-stringify@^2.3.1: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +saxes@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d" + integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw== + dependencies: + xmlchars "^2.2.0" + schema-utils@^3.1.1, schema-utils@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" @@ -5905,6 +6268,11 @@ schema-utils@^3.1.1, schema-utils@^3.2.0: ajv "^6.12.5" ajv-keywords "^3.5.2" +seedrandom@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-3.0.5.tgz#54edc85c95222525b0c7a6f6b3543d8e0b3aa0a7" + integrity sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg== + semver@7.x, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: version "7.6.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" @@ -5977,6 +6345,11 @@ set-function-length@^1.2.1: gopd "^1.0.1" has-property-descriptors "^1.0.2" +setimmediate@^1.0.5, setimmediate@~1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" + integrity sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA== + setprototypeof@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" @@ -6007,15 +6380,6 @@ shebang-regex@^3.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== -shelljs@0.8.5: - version "0.8.5" - resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" - integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== - dependencies: - glob "^7.0.0" - interpret "^1.0.0" - rechoir "^0.6.2" - side-channel@^1.0.4, side-channel@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" @@ -6074,7 +6438,7 @@ source-map-support@0.5.13: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-support@0.5.21, source-map-support@^0.5.20, source-map-support@~0.5.20: +source-map-support@^0.5.20, source-map-support@~0.5.20: version "0.5.21" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== @@ -6184,9 +6548,9 @@ string-width@^5.0.1, string-width@^5.1.2: strip-ansi "^7.0.1" string-width@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.1.0.tgz#d994252935224729ea3719c49f7206dc9c46550a" - integrity sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw== + version "7.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-7.2.0.tgz#b5bb8e2165ce275d4d43476dd2700ad9091db6dc" + integrity sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ== dependencies: emoji-regex "^10.3.0" get-east-asian-width "^1.0.0" @@ -6297,10 +6661,10 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -swagger-ui-dist@5.11.2: - version "5.11.2" - resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.11.2.tgz#b423e820928df703586ff58f80b09ffcf2434e08" - integrity sha512-jQG0cRgJNMZ7aCoiFofnoojeSaa/+KgWaDlfgs8QN+BXoGMpxeMVY5OEnjq4OlNvF3yjftO8c9GRAgcHlO+u7A== +swagger-ui-dist@5.17.14: + version "5.17.14" + resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-5.17.14.tgz#e2c222e5bf9e15ccf80ec4bc08b4aaac09792fd6" + integrity sha512-CVbSfaLpstV65OnSjbXfVd6Sta3q3F7Cj/yYuvHMp1P90LztOLs6PfUnKEVAeiIVQt9u2SaPwv0LiH/OyMjHRw== symbol-observable@4.0.0: version "4.0.0" @@ -6312,6 +6676,17 @@ tapable@^2.1.1, tapable@^2.2.0, tapable@^2.2.1: resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== +tar-stream@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" + integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== + dependencies: + bl "^4.0.3" + end-of-stream "^1.4.1" + fs-constants "^1.0.0" + inherits "^2.0.3" + readable-stream "^3.1.1" + tar@^6.1.11: version "6.2.1" resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" @@ -6336,9 +6711,9 @@ terser-webpack-plugin@^5.3.10: terser "^5.26.0" terser@^5.26.0: - version "5.31.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.0.tgz#06eef86f17007dbad4593f11a574c7f5eb02c6a1" - integrity sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg== + version "5.31.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.1.tgz#735de3c987dd671e95190e6b98cfe2f07f3cf0d4" + integrity sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg== dependencies: "@jridgewell/source-map" "^0.3.3" acorn "^8.8.2" @@ -6385,6 +6760,11 @@ through@^2.3.6: resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== +tiny-emitter@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423" + integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== + title-case@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/title-case/-/title-case-3.0.3.tgz#bc689b46f02e411f1d1e1d081f7c3deca0489982" @@ -6399,6 +6779,11 @@ tmp@^0.0.33: dependencies: os-tmpdir "~1.0.2" +tmp@^0.2.0: + version "0.2.3" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.2.3.tgz#eb783cc22bc1e8bebd0671476d46ea4eb32a79ae" + integrity sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w== + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -6433,6 +6818,11 @@ tr46@~0.0.3: resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== +"traverse@>=0.3.0 <0.4": + version "0.3.9" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9" + integrity sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ== + tree-kill@1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" @@ -6509,10 +6899,10 @@ tsconfig-paths@4.2.0, tsconfig-paths@^4.1.2: minimist "^1.2.6" strip-bom "^3.0.0" -tslib@2.6.2, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.5.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" - integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== +tslib@2.6.3, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.5.0: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== tslib@^1.8.1: version "1.14.1" @@ -6556,6 +6946,11 @@ type-is@^1.6.4, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +typed-function@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/typed-function/-/typed-function-4.2.1.tgz#19aa51847aa2dea9ef5e7fb7641c060179a74426" + integrity sha512-EGjWssW7Tsk4DGfE+5yluuljS1OGYWiI1J6e8puZz9nTMM51Oug8CD5Zo4gWMsOhq5BI+1bF+rWTm4Vbj3ivRA== + typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" @@ -6605,9 +7000,9 @@ typescript@^4.7.4: integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== uglify-js@^3.1.4: - version "3.17.4" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" - integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== + version "3.18.0" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.18.0.tgz#73b576a7e8fda63d2831e293aeead73e0a270deb" + integrity sha512-SyVVbcNBCk0dzr9XL/R/ySrmYf0s372K6/hFklzgcp2lBFyXtw4I7BOdDjlLhE1aVqaI/SHWXWmYdlZxuyF38A== uid@2.0.2: version "2.0.2" @@ -6649,10 +7044,26 @@ unpipe@1.0.0, unpipe@~1.0.0: resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== -update-browserslist-db@^1.0.13: - version "1.0.16" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz#f6d489ed90fb2f07d67784eb3f53d7891f736356" - integrity sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ== +unzipper@^0.10.11: + version "0.10.14" + resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.10.14.tgz#d2b33c977714da0fbc0f82774ad35470a7c962b1" + integrity sha512-ti4wZj+0bQTiX2KmKWuwj7lhV+2n//uXEotUmGuQqrbVZSEGFMbI68+c6JCQ8aAmUWYvtHEz2A8K6wXvueR/6g== + dependencies: + big-integer "^1.6.17" + binary "~0.3.0" + bluebird "~3.4.1" + buffer-indexof-polyfill "~1.0.0" + duplexer2 "~0.1.4" + fstream "^1.0.12" + graceful-fs "^4.2.2" + listenercount "~1.0.1" + readable-stream "~2.3.6" + setimmediate "~1.0.4" + +update-browserslist-db@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== dependencies: escalade "^3.1.2" picocolors "^1.0.1" @@ -6693,20 +7104,30 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@9.0.1, uuid@^9.0.0, uuid@^9.0.1: +uuid@10.0.0, uuid@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294" + integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== + +uuid@9.0.1, uuid@^9.0.0: version "9.0.1" resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== +uuid@^8.3.0: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-compile-cache-lib@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== v8-to-istanbul@^9.0.1: - version "9.2.0" - resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.2.0.tgz#2ed7644a245cddd83d4e087b9b33b3e62dfd10ad" - integrity sha512-/EH/sDgxU2eGxajKdwLCDmQ4FWq+kpi3uCmBGpw1xJtnAxEjlD8j8PEiGWpCIMIs3ciNAgH0d3TTJiUkYzyZjA== + version "9.3.0" + resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-9.3.0.tgz#b9572abfa62bd556c16d75fdebc1a411d5ff3175" + integrity sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA== dependencies: "@jridgewell/trace-mapping" "^0.3.12" "@types/istanbul-lib-coverage" "^2.0.1" @@ -6734,7 +7155,7 @@ walker@^1.0.8: dependencies: makeerror "1.0.12" -watchpack@^2.4.0: +watchpack@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.1.tgz#29308f2cac150fa8e4c92f90e0ec954a9fed7fff" integrity sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg== @@ -6769,26 +7190,26 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@5.90.1: - version "5.90.1" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.90.1.tgz#62ab0c097d7cbe83d32523dbfbb645cdb7c3c01c" - integrity sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog== +webpack@5.92.1: + version "5.92.1" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.92.1.tgz#eca5c1725b9e189cffbd86e8b6c3c7400efc5788" + integrity sha512-JECQ7IwJb+7fgUFBlrJzbyu3GEuNBcdqr1LD7IbSzwkSmIevTm8PF+wej3Oxuz/JFBUZ6O1o43zsPkwm1C4TmA== dependencies: "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.5" - "@webassemblyjs/ast" "^1.11.5" - "@webassemblyjs/wasm-edit" "^1.11.5" - "@webassemblyjs/wasm-parser" "^1.11.5" + "@webassemblyjs/ast" "^1.12.1" + "@webassemblyjs/wasm-edit" "^1.12.1" + "@webassemblyjs/wasm-parser" "^1.12.1" acorn "^8.7.1" - acorn-import-assertions "^1.9.0" + acorn-import-attributes "^1.9.5" browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.15.0" + enhanced-resolve "^5.17.0" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" + graceful-fs "^4.2.11" json-parse-even-better-errors "^2.3.1" loader-runner "^4.2.0" mime-types "^2.1.27" @@ -6796,7 +7217,7 @@ webpack@5.90.1: schema-utils "^3.2.0" tapable "^2.1.1" terser-webpack-plugin "^5.3.10" - watchpack "^2.4.0" + watchpack "^2.4.1" webpack-sources "^3.2.3" whatwg-url@^5.0.0: @@ -6901,6 +7322,11 @@ write-file-atomic@^4.0.2: imurmurhash "^0.1.4" signal-exit "^3.0.7" +xmlchars@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" + integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== + xtend@^4.0.0: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" @@ -6996,3 +7422,17 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +yoctocolors-cjs@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz#f4b905a840a37506813a7acaa28febe97767a242" + integrity sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA== + +zip-stream@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/zip-stream/-/zip-stream-4.1.1.tgz#1337fe974dbaffd2fa9a1ba09662a66932bd7135" + integrity sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ== + dependencies: + archiver-utils "^3.0.4" + compress-commons "^4.1.2" + readable-stream "^3.6.0"