diff --git a/src/app.module.ts b/src/app.module.ts index 0493452..61a1a96 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -32,6 +32,8 @@ import { SeasonTypeModule } from './modules/season-related/season-type/season-ty import { SeasonTypeModel } from './modules/season-related/season-type/data/models/season-type.model'; import { TaxModule } from './modules/transaction/tax/tax.module'; import { TaxModel } from './modules/transaction/tax/data/models/tax.model'; +import { SalesPriceFormulaModule } from './modules/transaction/sales-price-formula/sales-price-formula.module'; +import { SalesPriceFormulaModel } from './modules/transaction/sales-price-formula/data/models/sales-price-formula.model'; @Module({ imports: [ @@ -53,6 +55,7 @@ import { TaxModel } from './modules/transaction/tax/data/models/tax.model'; ItemModel, ItemCategoryModel, LogModel, + SalesPriceFormulaModel, SeasonTypeModel, TaxModel, UserModel, @@ -78,6 +81,7 @@ import { TaxModel } from './modules/transaction/tax/data/models/tax.model'; ItemModule, // transaction + SalesPriceFormulaModule, TaxModule, VipCategoryModule, VipCodeModule, diff --git a/src/core/strings/constants/default-data.constants.ts b/src/core/strings/constants/default-data.constants.ts index b3d80dd..266ee7f 100644 --- a/src/core/strings/constants/default-data.constants.ts +++ b/src/core/strings/constants/default-data.constants.ts @@ -1,5 +1,6 @@ import { UserRole } from 'src/modules/user-related/user/constants'; import { STATUS } from './base.constants'; +import { FormulaType } from 'src/modules/transaction/sales-price-formula/constants'; export const default_admin = { id: 'c59f811e-873c-4472-bd58-21c111902114', @@ -9,3 +10,13 @@ export const default_admin = { status: STATUS.ACTIVE, role: UserRole.SUPERADMIN, }; + +export const default_sales_formula = { + type: FormulaType.SALES_PRICE, + id: '520f405d-f41e-4d56-82fb-74d64c854f49', +}; + +export const default_profit_formula = { + type: FormulaType.PROFIT_SHARE, + id: '6c7d590f-8051-44a5-8f81-2641765b4609', +}; diff --git a/src/core/strings/constants/table.constants.ts b/src/core/strings/constants/table.constants.ts index 40f1551..a97570b 100644 --- a/src/core/strings/constants/table.constants.ts +++ b/src/core/strings/constants/table.constants.ts @@ -3,6 +3,7 @@ export enum TABLE_NAME { ITEM = 'items', ITEM_CATEGORY = 'item_categories', LOG = 'logs', + PRICE_FORMULA = 'price_formulas', SEASON_TYPE = 'season_types', TAX = 'taxes', TENANT = 'tenants', diff --git a/src/database/migrations/1718164344363-sales-price-formula.ts b/src/database/migrations/1718164344363-sales-price-formula.ts new file mode 100644 index 0000000..16a276f --- /dev/null +++ b/src/database/migrations/1718164344363-sales-price-formula.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class SalesPriceFormula1718164344363 implements MigrationInterface { + name = 'SalesPriceFormula1718164344363'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TYPE "public"."price_formulas_type_enum" AS ENUM('sales price', 'profit share')`, + ); + await queryRunner.query( + `CREATE TABLE "price_formulas" ("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, "type" "public"."price_formulas_type_enum" NOT NULL DEFAULT 'sales price', "formula_render" json, "formula_string" character varying, "example_formula" character varying, "example_result" numeric, CONSTRAINT "PK_16b91da2a4659999a3c815e9cf6" PRIMARY KEY ("id"))`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`DROP TABLE "price_formulas"`); + await queryRunner.query(`DROP TYPE "public"."price_formulas_type_enum"`); + } +} diff --git a/src/database/seeds/1718164659-default-formula.seed.ts b/src/database/seeds/1718164659-default-formula.seed.ts new file mode 100644 index 0000000..461bc7f --- /dev/null +++ b/src/database/seeds/1718164659-default-formula.seed.ts @@ -0,0 +1,36 @@ +import { + default_profit_formula, + default_sales_formula, +} from 'src/core/strings/constants/default-data.constants'; +import { SalesPriceFormulaModel } from 'src/modules/transaction/sales-price-formula/data/models/sales-price-formula.model'; +import { Connection } from 'typeorm'; +import { Factory, Seeder } from 'typeorm-seeding'; + +export class SeedDefaultFormula implements Seeder { + public async run(factory: Factory, connection: Connection): Promise { + try { + const sales_formula = new SalesPriceFormulaModel(); + Object.assign(sales_formula, { + ...default_sales_formula, + created_at: new Date().getTime(), + updated_at: new Date().getTime(), + }); + + const profit_formula = new SalesPriceFormulaModel(); + Object.assign(profit_formula, { + ...default_profit_formula, + created_at: new Date().getTime(), + updated_at: new Date().getTime(), + }); + + await connection + .createQueryBuilder() + .insert() + .into(SalesPriceFormulaModel) + .values([sales_formula, profit_formula]) + .execute(); + } catch (error) { + console.log(error, 'er'); + } + } +} diff --git a/src/modules/transaction/sales-price-formula/constants.ts b/src/modules/transaction/sales-price-formula/constants.ts new file mode 100644 index 0000000..332a289 --- /dev/null +++ b/src/modules/transaction/sales-price-formula/constants.ts @@ -0,0 +1,4 @@ +export enum FormulaType { + SALES_PRICE = 'sales price', + PROFIT_SHARE = 'profit share', +} diff --git a/src/modules/transaction/sales-price-formula/data/models/sales-price-formula.model.ts b/src/modules/transaction/sales-price-formula/data/models/sales-price-formula.model.ts new file mode 100644 index 0000000..4793e8b --- /dev/null +++ b/src/modules/transaction/sales-price-formula/data/models/sales-price-formula.model.ts @@ -0,0 +1,30 @@ +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; +import { SalesPriceFormulaEntity } from '../../domain/entities/sales-price-formula.entity'; +import { Column, Entity } from 'typeorm'; +import { BaseModel } from 'src/core/modules/data/model/base.model'; +import { FormulaType } from '../../constants'; + +@Entity(TABLE_NAME.PRICE_FORMULA) +export class SalesPriceFormulaModel + extends BaseModel + implements SalesPriceFormulaEntity +{ + @Column('enum', { + name: 'type', + enum: FormulaType, + default: FormulaType.SALES_PRICE, + }) + type: FormulaType; + + @Column('json', { name: 'formula_render', nullable: true }) + formula_render: any; + + @Column('varchar', { name: 'formula_string', nullable: true }) + formula_string: string; + + @Column('varchar', { name: 'example_formula', nullable: true }) + example_formula: string; + + @Column('numeric', { name: 'example_result', nullable: true }) + example_result: number; +} diff --git a/src/modules/transaction/sales-price-formula/data/services/sales-price-formula-data.service.ts b/src/modules/transaction/sales-price-formula/data/services/sales-price-formula-data.service.ts new file mode 100644 index 0000000..5ed3752 --- /dev/null +++ b/src/modules/transaction/sales-price-formula/data/services/sales-price-formula-data.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; +import { SalesPriceFormulaEntity } from '../../domain/entities/sales-price-formula.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { SalesPriceFormulaModel } from '../models/sales-price-formula.model'; +import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; +import { Repository } from 'typeorm'; + +@Injectable() +export class SalesPriceFormulaDataService extends BaseDataService { + constructor( + @InjectRepository(SalesPriceFormulaModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/transaction/sales-price-formula/data/services/sales-price-formula-read.service.ts b/src/modules/transaction/sales-price-formula/data/services/sales-price-formula-read.service.ts new file mode 100644 index 0000000..8bb9ed8 --- /dev/null +++ b/src/modules/transaction/sales-price-formula/data/services/sales-price-formula-read.service.ts @@ -0,0 +1,17 @@ +import { Injectable } from '@nestjs/common'; +import { SalesPriceFormulaEntity } from '../../domain/entities/sales-price-formula.entity'; +import { InjectRepository } from '@nestjs/typeorm'; +import { SalesPriceFormulaModel } from '../models/sales-price-formula.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 SalesPriceFormulaReadService extends BaseReadService { + constructor( + @InjectRepository(SalesPriceFormulaModel, CONNECTION_NAME.DEFAULT) + private repo: Repository, + ) { + super(repo); + } +} diff --git a/src/modules/transaction/sales-price-formula/domain/entities/event/sales-price-formula-created.event.ts b/src/modules/transaction/sales-price-formula/domain/entities/event/sales-price-formula-created.event.ts new file mode 100644 index 0000000..e5823da --- /dev/null +++ b/src/modules/transaction/sales-price-formula/domain/entities/event/sales-price-formula-created.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class SalesPriceFormulaCreatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/transaction/sales-price-formula/domain/entities/event/sales-price-formula-deleted.event.ts b/src/modules/transaction/sales-price-formula/domain/entities/event/sales-price-formula-deleted.event.ts new file mode 100644 index 0000000..e843e0c --- /dev/null +++ b/src/modules/transaction/sales-price-formula/domain/entities/event/sales-price-formula-deleted.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class SalesPriceFormulaDeletedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/transaction/sales-price-formula/domain/entities/event/sales-price-formula-updated.event.ts b/src/modules/transaction/sales-price-formula/domain/entities/event/sales-price-formula-updated.event.ts new file mode 100644 index 0000000..4972a61 --- /dev/null +++ b/src/modules/transaction/sales-price-formula/domain/entities/event/sales-price-formula-updated.event.ts @@ -0,0 +1,5 @@ +import { IEvent } from 'src/core/strings/constants/interface.constants'; + +export class SalesPriceFormulaUpdatedEvent { + constructor(public readonly data: IEvent) {} +} diff --git a/src/modules/transaction/sales-price-formula/domain/entities/sales-price-formula.entity.ts b/src/modules/transaction/sales-price-formula/domain/entities/sales-price-formula.entity.ts new file mode 100644 index 0000000..c0adb62 --- /dev/null +++ b/src/modules/transaction/sales-price-formula/domain/entities/sales-price-formula.entity.ts @@ -0,0 +1,10 @@ +import { BaseEntity } from 'src/core/modules/domain/entities/base.entity'; +import { FormulaType } from '../../constants'; + +export interface SalesPriceFormulaEntity extends BaseEntity { + formula_render: any; // json type + formula_string: string; // digunakan untuk menyimpan string dari formula + example_formula: string; + example_result: number; + type: FormulaType; +} diff --git a/src/modules/transaction/sales-price-formula/domain/usecases/managers/detail-sales-price-formula.manager.ts b/src/modules/transaction/sales-price-formula/domain/usecases/managers/detail-sales-price-formula.manager.ts new file mode 100644 index 0000000..55d41ea --- /dev/null +++ b/src/modules/transaction/sales-price-formula/domain/usecases/managers/detail-sales-price-formula.manager.ts @@ -0,0 +1,50 @@ +import { Injectable } from '@nestjs/common'; +import { BaseDetailManager } from 'src/core/modules/domain/usecase/managers/base-detail.manager'; +import { SalesPriceFormulaEntity } from '../../entities/sales-price-formula.entity'; +import { RelationParam } from 'src/core/modules/domain/entities/base-filter.entity'; +import { FormulaType } from '../../../constants'; + +@Injectable() +export class DetailSalesPriceFormulaManager 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}.formula_render`, + `${this.tableName}.formula_string`, + `${this.tableName}.editor_name`, + `${this.tableName}.updated_at`, + `${this.tableName}.created_at`, + ]; + } + + get setFindProperties(): any { + return { + type: FormulaType.SALES_PRICE, + }; + } +} 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 new file mode 100644 index 0000000..8ec63ce --- /dev/null +++ b/src/modules/transaction/sales-price-formula/domain/usecases/managers/update-sales-price-formula.manager.ts @@ -0,0 +1,49 @@ +import { Injectable } from '@nestjs/common'; +import { BaseUpdateManager } from 'src/core/modules/domain/usecase/managers/base-update.manager'; +import { SalesPriceFormulaEntity } from '../../entities/sales-price-formula.entity'; +import { SalesPriceFormulaModel } from '../../../data/models/sales-price-formula.model'; +import { SalesPriceFormulaUpdatedEvent } from '../../entities/event/sales-price-formula-updated.event'; +import { + EventTopics, + columnUniques, + validateRelations, +} from 'src/core/strings/constants/interface.constants'; + +@Injectable() +export class UpdateSalesPriceFormulaManager extends BaseUpdateManager { + async validateProcess(): Promise { + return; + } + + async beforeProcess(): Promise { + Object.assign(this.data, { + formula_string: JSON.stringify(this.data.formula_render), + }); + return; + } + + async afterProcess(): Promise { + return; + } + + get validateRelations(): validateRelations[] { + return []; + } + + get uniqueColumns(): columnUniques[] { + return []; + } + + get entityTarget(): any { + return SalesPriceFormulaModel; + } + + get eventTopics(): EventTopics[] { + return [ + { + topic: SalesPriceFormulaUpdatedEvent, + data: this.data, + }, + ]; + } +} 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 new file mode 100644 index 0000000..3b17c87 --- /dev/null +++ b/src/modules/transaction/sales-price-formula/domain/usecases/sales-price-formula-data.orchestrator.ts @@ -0,0 +1,27 @@ +import { Injectable } from '@nestjs/common'; +import { SalesPriceFormulaDataService } from '../../data/services/sales-price-formula-data.service'; +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'; + +@Injectable() +export class SalesPriceFormulaDataOrchestrator { + constructor( + private updateManager: UpdateSalesPriceFormulaManager, + private serviceData: SalesPriceFormulaDataService, + ) {} + + async update(data): Promise { + const formula = await this.serviceData.getOneByOptions({ + where: { + type: FormulaType.SALES_PRICE, + }, + }); + + this.updateManager.setData(formula.id, data); + this.updateManager.setService(this.serviceData, TABLE_NAME.PRICE_FORMULA); + await this.updateManager.execute(); + return this.updateManager.getResult(); + } +} diff --git a/src/modules/transaction/sales-price-formula/domain/usecases/sales-price-formula-read.orchestrator.ts b/src/modules/transaction/sales-price-formula/domain/usecases/sales-price-formula-read.orchestrator.ts new file mode 100644 index 0000000..4923bcc --- /dev/null +++ b/src/modules/transaction/sales-price-formula/domain/usecases/sales-price-formula-read.orchestrator.ts @@ -0,0 +1,20 @@ +import { Injectable } from '@nestjs/common'; +import { SalesPriceFormulaReadService } from '../../data/services/sales-price-formula-read.service'; +import { SalesPriceFormulaEntity } from '../entities/sales-price-formula.entity'; +import { DetailSalesPriceFormulaManager } from './managers/detail-sales-price-formula.manager'; +import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; + +@Injectable() +export class SalesPriceFormulaReadOrchestrator { + constructor( + private detailManager: DetailSalesPriceFormulaManager, + private serviceData: SalesPriceFormulaReadService, + ) {} + + async detail(): Promise { + this.detailManager.setData(''); + this.detailManager.setService(this.serviceData, TABLE_NAME.PRICE_FORMULA); + await this.detailManager.execute(); + return this.detailManager.getResult(); + } +} diff --git a/src/modules/transaction/sales-price-formula/index.ts b/src/modules/transaction/sales-price-formula/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/modules/transaction/sales-price-formula/infrastructure/dto/sales-price-formula.dto.ts b/src/modules/transaction/sales-price-formula/infrastructure/dto/sales-price-formula.dto.ts new file mode 100644 index 0000000..4cc7542 --- /dev/null +++ b/src/modules/transaction/sales-price-formula/infrastructure/dto/sales-price-formula.dto.ts @@ -0,0 +1,35 @@ +import { BaseDto } from 'src/core/modules/infrastructure/dto/base.dto'; +import { SalesPriceFormulaEntity } from '../../domain/entities/sales-price-formula.entity'; +import { ApiProperty } from '@nestjs/swagger'; +import { ValidateIf } from 'class-validator'; +import { Exclude } from 'class-transformer'; +import { Any } from 'typeorm'; +import { FormulaType } from '../../constants'; + +export class SalesPriceFormulaDto + extends BaseDto + implements SalesPriceFormulaEntity +{ + @ApiProperty({ + type: Any, + required: false, + }) + @ValidateIf((body) => body.formula_render) + formula_render: any; + + @ApiProperty({ + type: String, + required: false, + }) + @ValidateIf((body) => body.formula_string) + formula_string: string; + + @Exclude() + example_formula: string; + + @Exclude() + example_result: number; + + @Exclude() + type: FormulaType; +} diff --git a/src/modules/transaction/sales-price-formula/infrastructure/sales-price-formula-data.controller.ts b/src/modules/transaction/sales-price-formula/infrastructure/sales-price-formula-data.controller.ts new file mode 100644 index 0000000..f4dc3a8 --- /dev/null +++ b/src/modules/transaction/sales-price-formula/infrastructure/sales-price-formula-data.controller.ts @@ -0,0 +1,21 @@ +import { Body, Controller, Post } from '@nestjs/common'; +import { SalesPriceFormulaDataOrchestrator } from '../domain/usecases/sales-price-formula-data.orchestrator'; +import { SalesPriceFormulaDto } from './dto/sales-price-formula.dto'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { SalesPriceFormulaEntity } from '../domain/entities/sales-price-formula.entity'; +import { Public } from 'src/core/guards'; + +@ApiTags(`sales price formulas - data`) +@Controller('sales-price-formula') +@Public(false) +@ApiBearerAuth('JWT') +export class SalesPriceFormulaDataController { + constructor(private orchestrator: SalesPriceFormulaDataOrchestrator) {} + + @Post() + async create( + @Body() data: SalesPriceFormulaDto, + ): Promise { + return await this.orchestrator.update(data); + } +} diff --git a/src/modules/transaction/sales-price-formula/infrastructure/sales-price-formula-read.controller.ts b/src/modules/transaction/sales-price-formula/infrastructure/sales-price-formula-read.controller.ts new file mode 100644 index 0000000..9e60613 --- /dev/null +++ b/src/modules/transaction/sales-price-formula/infrastructure/sales-price-formula-read.controller.ts @@ -0,0 +1,18 @@ +import { Controller, Get } from '@nestjs/common'; +import { SalesPriceFormulaEntity } from '../domain/entities/sales-price-formula.entity'; +import { SalesPriceFormulaReadOrchestrator } from '../domain/usecases/sales-price-formula-read.orchestrator'; +import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; +import { Public } from 'src/core/guards'; + +@ApiTags(`sales price formulas - read`) +@Controller('sales-price-formula') +@Public(false) +@ApiBearerAuth('JWT') +export class SalesPriceFormulaReadController { + constructor(private orchestrator: SalesPriceFormulaReadOrchestrator) {} + + @Get() + async detail(): Promise { + return await this.orchestrator.detail(); + } +} 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 new file mode 100644 index 0000000..0fc590a --- /dev/null +++ b/src/modules/transaction/sales-price-formula/sales-price-formula.module.ts @@ -0,0 +1,37 @@ +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 { SalesPriceFormulaDataService } from './data/services/sales-price-formula-data.service'; +import { SalesPriceFormulaReadService } from './data/services/sales-price-formula-read.service'; +import { SalesPriceFormulaReadController } from './infrastructure/sales-price-formula-read.controller'; +import { SalesPriceFormulaReadOrchestrator } from './domain/usecases/sales-price-formula-read.orchestrator'; +import { SalesPriceFormulaDataController } from './infrastructure/sales-price-formula-data.controller'; +import { SalesPriceFormulaDataOrchestrator } from './domain/usecases/sales-price-formula-data.orchestrator'; +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'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + TypeOrmModule.forFeature([SalesPriceFormulaModel], CONNECTION_NAME.DEFAULT), + CqrsModule, + ], + controllers: [ + SalesPriceFormulaDataController, + SalesPriceFormulaReadController, + ], + providers: [ + DetailSalesPriceFormulaManager, + UpdateSalesPriceFormulaManager, + + SalesPriceFormulaDataService, + SalesPriceFormulaReadService, + + SalesPriceFormulaDataOrchestrator, + SalesPriceFormulaReadOrchestrator, + ], +}) +export class SalesPriceFormulaModule {}