diff --git a/assets/email-template/redesign/change-date-information.html b/assets/email-template/redesign/change-date-information.html new file mode 100644 index 0000000..586aabb --- /dev/null +++ b/assets/email-template/redesign/change-date-information.html @@ -0,0 +1,2027 @@ + + + + + Change Date Information + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + diff --git a/assets/email-template/redesign/invoice-bank.html b/assets/email-template/redesign/invoice-bank.html new file mode 100644 index 0000000..858a633 --- /dev/null +++ b/assets/email-template/redesign/invoice-bank.html @@ -0,0 +1,2119 @@ + + + + + Invoice Bank Transfer + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + diff --git a/assets/email-template/redesign/invoice-expired.html b/assets/email-template/redesign/invoice-expired.html new file mode 100644 index 0000000..88488fa --- /dev/null +++ b/assets/email-template/redesign/invoice-expired.html @@ -0,0 +1,1962 @@ + + + + + Invoice Expired + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + diff --git a/assets/email-template/redesign/invoice-midtrans.html b/assets/email-template/redesign/invoice-midtrans.html new file mode 100644 index 0000000..98c250d --- /dev/null +++ b/assets/email-template/redesign/invoice-midtrans.html @@ -0,0 +1,2037 @@ + + + + + Invoice Midtrans + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + diff --git a/assets/email-template/redesign/payment-confirmation.html b/assets/email-template/redesign/payment-confirmation.html new file mode 100644 index 0000000..bdde960 --- /dev/null +++ b/assets/email-template/redesign/payment-confirmation.html @@ -0,0 +1,2454 @@ + + + + + Payment Confirmation + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + diff --git a/assets/email-template/redesign/refund-confirmation.html b/assets/email-template/redesign/refund-confirmation.html new file mode 100644 index 0000000..03a6ce5 --- /dev/null +++ b/assets/email-template/redesign/refund-confirmation.html @@ -0,0 +1,2832 @@ + + + + + Refund Confirmation + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + diff --git a/assets/email-template/redesign/refund-request.html b/assets/email-template/redesign/refund-request.html new file mode 100644 index 0000000..579340f --- /dev/null +++ b/assets/email-template/redesign/refund-request.html @@ -0,0 +1,2414 @@ + + + + + Refund Confirmation + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + +
+ + + + + + +
+
+ + diff --git a/assets/image/we.png b/assets/image/we.png new file mode 100644 index 0000000..cb44d28 Binary files /dev/null and b/assets/image/we.png differ diff --git a/src/app.module.ts b/src/app.module.ts index 2c9e87e..ca71ede 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -76,6 +76,7 @@ import { PosLogModel } from './modules/configuration/log/data/models/pos-log.mod import { ExportModule } from './modules/configuration/export/export.module'; import { TransactionDemographyModel } from './modules/transaction/transaction/data/models/transaction-demography.model'; import { SupersetModule } from './modules/configuration/superset/superset.module'; +import { GateScanModule } from './modules/gates/gate.module'; @Module({ imports: [ @@ -178,6 +179,8 @@ import { SupersetModule } from './modules/configuration/superset/superset.module // superset SupersetModule, + + GateScanModule, ], controllers: [], providers: [ diff --git a/src/core/response/constants.ts b/src/core/response/constants.ts index 1da092d..ae59a5a 100644 --- a/src/core/response/constants.ts +++ b/src/core/response/constants.ts @@ -1 +1,2 @@ export const PAGINATION_RESPONSE = 'PAGINATION_RESPONSE'; +export const GATE_RESPONSE = 'GATE_RESPONSE'; diff --git a/src/core/response/domain/decorators/pagination.response.ts b/src/core/response/domain/decorators/pagination.response.ts index 482db0c..b9ba684 100644 --- a/src/core/response/domain/decorators/pagination.response.ts +++ b/src/core/response/domain/decorators/pagination.response.ts @@ -1,5 +1,5 @@ import { SetMetadata } from '@nestjs/common'; -import { PAGINATION_RESPONSE } from '../../constants'; +import { GATE_RESPONSE, PAGINATION_RESPONSE } from '../../constants'; /** * This decorator will tell the response, @@ -7,3 +7,5 @@ import { PAGINATION_RESPONSE } from '../../constants'; */ export const Pagination = (isPagination = true) => SetMetadata(PAGINATION_RESPONSE, isPagination); + +export const Gate = () => SetMetadata(GATE_RESPONSE, true); diff --git a/src/core/response/domain/response.interceptor.ts b/src/core/response/domain/response.interceptor.ts index f9893d4..9f4d8de 100644 --- a/src/core/response/domain/response.interceptor.ts +++ b/src/core/response/domain/response.interceptor.ts @@ -8,13 +8,20 @@ import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { Request } from 'express'; import { Reflector } from '@nestjs/core'; -import { PAGINATION_RESPONSE } from '../constants'; +import { GATE_RESPONSE, PAGINATION_RESPONSE } from '../constants'; import { createPaginationResponse } from './utils/pagination-meta.helper'; @Injectable() export class TransformInterceptor implements NestInterceptor { constructor(protected readonly reflector: Reflector) {} intercept(context: ExecutionContext, next: CallHandler): Observable { + const isGate = this.reflector.getAllAndOverride(GATE_RESPONSE, [ + context.getHandler(), + context.getClass(), + ]); + + if (isGate) return next.handle(); + const isPagination = this.reflector.getAllAndOverride( PAGINATION_RESPONSE, [context.getHandler(), context.getClass()], diff --git a/src/database/migrations/1724926316235-add-value-variable-formula.ts b/src/database/migrations/1724926316235-add-value-variable-formula.ts new file mode 100644 index 0000000..b10b372 --- /dev/null +++ b/src/database/migrations/1724926316235-add-value-variable-formula.ts @@ -0,0 +1,19 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddValueVariableFormula1724926316235 + implements MigrationInterface +{ + name = 'AddValueVariableFormula1724926316235'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "price_formulas" ADD "value_for" character varying NOT NULL DEFAULT 'dpp'`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `ALTER TABLE "price_formulas" DROP COLUMN "value_for"`, + ); + } +} diff --git a/src/modules/configuration/mail/domain/handlers/payment-transaction.handler.ts b/src/modules/configuration/mail/domain/handlers/payment-transaction.handler.ts index 355c75b..f71341f 100644 --- a/src/modules/configuration/mail/domain/handlers/payment-transaction.handler.ts +++ b/src/modules/configuration/mail/domain/handlers/payment-transaction.handler.ts @@ -32,7 +32,6 @@ export class PaymentTransactionHandler const current_data = event.data.data; const data_id = current_data.transaction_id ?? event.data.id; const from_refund = event.data.module == TABLE_NAME.REFUND; - console.log('payment handlet', { data_id }); const payments = await this.paymentService.getManyByOptions({ where: { @@ -106,6 +105,15 @@ export class PaymentTransactionHandler `; })} `, + + refund_items_data: transaction?.['refund']?.refund_items + ?.filter((item) => Number(item.qty_refund) > 0) + .map((item) => { + return { + qty_refund: item.qty_refund, + item_name: item.transaction_item.item_name, + }; + }), }); } diff --git a/src/modules/configuration/mail/domain/helpers/send-email.helper.ts b/src/modules/configuration/mail/domain/helpers/send-email.helper.ts index ad0532a..2db8cd0 100644 --- a/src/modules/configuration/mail/domain/helpers/send-email.helper.ts +++ b/src/modules/configuration/mail/domain/helpers/send-email.helper.ts @@ -17,7 +17,7 @@ export async function sendEmail(receivers, invoiceType, attachment?) { for (const receiver of receivers) { try { const templateName = getTemplate(receiver.payment_type, invoiceType); - const templatePath = `./assets/email-template/${templateName}.html`; + const templatePath = `./assets/email-template/redesign/${templateName}.html`; const templateSource = fs.readFileSync(templatePath, 'utf8'); const template = handlebars.compile(templateSource); diff --git a/src/modules/gates/domain/entity/gate-request.entity.ts b/src/modules/gates/domain/entity/gate-request.entity.ts new file mode 100644 index 0000000..299e086 --- /dev/null +++ b/src/modules/gates/domain/entity/gate-request.entity.ts @@ -0,0 +1,5 @@ +export interface GateScanEntity { + gate_id: string; + type: string; + uuid: string; +} diff --git a/src/modules/gates/domain/entity/gate-response.entity.ts b/src/modules/gates/domain/entity/gate-response.entity.ts new file mode 100644 index 0000000..1d8019d --- /dev/null +++ b/src/modules/gates/domain/entity/gate-response.entity.ts @@ -0,0 +1,8 @@ +export interface GateResponseEntity { + code: number; + message: string; +} + +export interface GateMasterEntity { + codes: string[]; +} diff --git a/src/modules/gates/gate.module.ts b/src/modules/gates/gate.module.ts new file mode 100644 index 0000000..0f2577c --- /dev/null +++ b/src/modules/gates/gate.module.ts @@ -0,0 +1,17 @@ +import { Global, Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { GateController } from './infrastructure/gate.controller'; + +@Global() +@Module({ + imports: [ + ConfigModule.forRoot(), + // TypeOrmModule.forFeature( + // [], + // CONNECTION_NAME.DEFAULT, + // ), + ], + controllers: [GateController], + providers: [], +}) +export class GateScanModule {} diff --git a/src/modules/gates/infrastructure/gate.controller.ts b/src/modules/gates/infrastructure/gate.controller.ts new file mode 100644 index 0000000..74fa973 --- /dev/null +++ b/src/modules/gates/infrastructure/gate.controller.ts @@ -0,0 +1,94 @@ +import { Body, Controller, Get, Param, Post, Res } from '@nestjs/common'; +import { Response } from 'express'; +import { ApiTags } from '@nestjs/swagger'; +import { Public } from 'src/core/guards'; +import { GateScanEntity } from '../domain/entity/gate-request.entity'; +import { + GateMasterEntity, + GateResponseEntity, +} from '../domain/entity/gate-response.entity'; +import { Gate } from 'src/core/response'; + +const masterGates = [ + '319b6d3e-b661-4d19-8695-0dd6fb76465e', + '9afdb79d-7162-43e6-8ac6-f1941adea7ba', + '7e4c0281-8cf2-420e-aba1-c8ff834de450', + '19318ac8-caa0-47e4-8a41-2aac238d3665', + '495bc25f-42c4-4007-8e79-3747fa1054b6', + 'b90fc9a9-efd9-4216-a8af-7ed120b141de', + '4399e93c-a839-4802-a49d-f933c72b1433', + '970673a7-6370-444a-931a-9784220dd35d', + '151ab50e-4e54-4252-b3ab-f5c0817b27a0', + '4c0e6924-baf5-47fb-a15b-fd1cd0958cc0', +]; + +const failedGates = [ + 'b3c3ae7b-daf5-4340-998b-ee35ed41323d', + 'be157609-92b8-4989-920d-a81769bcb05a', +]; + +const gateResponses = [ + { + statusCode: 200, + code: 1, + message: 'Berhasil Check In', + }, + { + statusCode: 403, + code: 2, + message: 'Gagal melakukan Check In. Karena tiket telah kadaluarsa', + }, + { + statusCode: 403, + code: 3, + message: 'Gagal melakukan Check In. Tiket tidak tersedia', + }, +]; + +@ApiTags(`Gate - read`) +@Controller(`v1/gate`) +@Public(true) +@Gate() +export class GateController { + @Post('scan') + async scan( + @Body() data: GateScanEntity, + @Res({ passthrough: true }) res: Response, + ): Promise { + console.log(data); + if (masterGates.includes(data.uuid)) { + res.status(200); + return gateResponses[0]; + } + if (failedGates.includes(data.uuid)) { + res.status(403); + return gateResponses[2]; + } + + const response = Math.floor(Math.random() * 3); + const responseValue = gateResponses[response]; + + res.status(responseValue.statusCode); + return responseValue; + } + + @Get(':id/master') + async detail(@Param('id') id: string): Promise { + if (id == '1') return { codes: masterGates }; + return { + codes: this.createRandomStringArray(masterGates), + }; + } + + createRandomStringArray(inputArray: string[]): string[] { + const randomLength = Math.floor(Math.random() * 4) + 2; // Random length between 2 and 5 + const outputArray: string[] = []; + + while (outputArray.length < randomLength) { + const randomIndex = Math.floor(Math.random() * inputArray.length); + outputArray.push(inputArray[randomIndex]); + } + + return outputArray; + } +} diff --git a/src/modules/reports/shared/configs/transaction-report/configs/income-per-item-master.ts b/src/modules/reports/shared/configs/transaction-report/configs/income-per-item-master.ts new file mode 100644 index 0000000..69de3a5 --- /dev/null +++ b/src/modules/reports/shared/configs/transaction-report/configs/income-per-item-master.ts @@ -0,0 +1,340 @@ +import { + DATA_FORMAT, + DATA_TYPE, + FILTER_FIELD_TYPE, + FILTER_TYPE, + REPORT_GROUP, +} from '../../../constant'; +import { ReportConfigEntity } from '../../../entities/report-config.entity'; +import { TransactionType } from 'src/modules/transaction/transaction/constants'; +import { STATUS } from 'src/core/strings/constants/base.constants'; + +export default { + group_name: REPORT_GROUP.transaction_report, + unique_name: `${REPORT_GROUP.transaction_report}__income_per_item_master`, + label: 'Pendapatan Per Item Master', + table_schema: `transactions main + LEFT JOIN transaction_items tr_item ON tr_item.transaction_id::text = main.id::text + LEFT JOIN transaction_item_breakdowns tr_item_bundling ON tr_item_bundling.transaction_item_id::text = tr_item.id::text + LEFT JOIN refunds refund ON refund.transaction_id = main.id + LEFT JOIN items item ON item.id::text = tr_item.item_id::text + LEFT JOIN users tenant ON tenant.id::text = item.tenant_id::text + LEFT JOIN refund_items refund_item ON refund_item.refund_item_id::text = tr_item.item_id::text`, + main_table_alias: 'main', + whereDefaultConditions: [ + { + column: 'main.status', + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + values: [STATUS.SETTLED, STATUS.REFUNDED, STATUS.PROCESS_REFUND], + }, + ], + defaultOrderBy: [], + lowLevelOrderBy: [], + filter_period_config: { + hidden: true, + }, + + column_configs: [ + { + column: 'main__settlement_date', + query: 'main.settlement_date', + label: 'Tanggal Pendapatan', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_TIMESTAMP, + date_format: 'DD/MM/YYYY', + }, + { + column: 'item_owner', + query: `CASE WHEN tenant.name is not null THEN tenant.name ELSE 'Company' END`, + label: 'Kepemilikan', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__type', + query: 'main.type', + label: 'Sumber', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__invoice_code', + query: 'main.invoice_code', + label: 'Kode Booking', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__payment_code', + query: 'main.payment_code', + label: 'Kode Pembayaran', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'tr_item__item_category_name', + query: 'tr_item.item_category_name', + label: 'Kategori Item', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'tr_item__item_name', + query: 'tr_item.item_name', + label: 'Nama Item', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'tr_item__breakdown_bundling', + query: 'tr_item.breakdown_bundling', + label: 'Breakdown Bundling', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.BOOLEAN, + }, + { + column: 'tr_item_bundling__item_name', + query: 'tr_item_bundling.item_name', + label: 'Nama Item Bundling', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__customer_type', + query: 'main.customer_type', + label: 'Tipe Pelanggan', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'main__creator_counter_no', + query: 'main.creator_counter_no', + label: 'No.PoS', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'tr_item__qty', + query: 'tr_item.qty', + label: 'Qty', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.NUMBER, + }, + { + column: 'tr_item__total_hpp', + query: 'tr_item.total_hpp', + label: 'Total HPP', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + // TODO => tambahkan total dpp per item + // TODO => tambahkan total tax + { + column: 'tr_item__total_price', + query: 'tr_item.total_price', + label: 'Total Penjualan', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'refund__refund_date', + query: 'refund.refund_date', + label: 'Tanggal Pengembalian', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.DATE_TIMESTAMP, + date_format: 'DD/MM/YYYY', + }, + { + column: 'refund__status', + query: 'refund.status', + label: 'Status Pengembalian', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'refund__code', + query: 'refund.code', + label: 'Kode Pengembalian', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.TEXT, + }, + { + column: 'refund_item__qty_refund', + query: 'refund_item.qty_refund', + label: 'Qty Pengembalian', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.NUMBER, + }, + { + column: 'refund_item__refund_total', + query: '(refund_item.refund_total * -1)', + label: 'Total Pengembalian', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + + { + column: 'transaction_balance', + query: `CASE WHEN refund.id is null THEN tr_item.total_price ELSE tr_item.total_price - refund_item.refund_total END`, + label: 'Balance', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'tr_item__item_tenant_share_margin', + query: 'tr_item.item_tenant_share_margin', + label: 'Profile Share (IDR)', + type: DATA_TYPE.MEASURE, + format: DATA_FORMAT.CURRENCY, + }, + { + column: 'tenant_income', + query: 'tr_item.total_price - tr_item.item_tenant_share_margin', + label: 'Pendapatan Tenant', + type: DATA_TYPE.DIMENSION, + format: DATA_FORMAT.CURRENCY, + }, + + { + column: 'main__customer_name', + query: 'main.customer_name', + label: 'Nama Pelanggan', + 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__customer_phone', + query: 'main.customer_phone', + label: 'Telepon', + 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, + }, + ], + whereCondition(filterModel) { + const queryFilter = []; + const breakdown = filterModel.tr_item__breakdown_bundling; + if (breakdown) { + const value = breakdown.filter.map((item) => { + return item === 'Yes' ? true : false; + }); + + queryFilter.push(`tr_item.breakdown_bundling in (${value.join()})`); + } + return queryFilter; + }, + ignore_filter_keys: ['tr_item__breakdown_bundling'], + filter_configs: [ + { + filed_label: 'Tanggal Pendapatan', + filter_column: 'main__settlement_date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, + }, + { + filed_label: 'Kepemilikan', + filter_column: 'item_owner', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Sumber', + filter_column: 'main__type', + field_type: FILTER_FIELD_TYPE.select, + filter_type: FILTER_TYPE.TEXT_IN_MEMBER, + select_custom_options: [...Object.values(TransactionType)], + }, + { + filed_label: 'Kode Booking', + filter_column: 'main__invoice_code', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Kode Pembayaran', + filter_column: 'main__payment_code', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Kategori Item', + filter_column: 'tr_item__item_category_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Nama Item', + filter_column: 'tr_item__item_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Breakdown Item', + filter_column: 'tr_item__breakdown_bundling', + field_type: FILTER_FIELD_TYPE.select, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + select_custom_options: ['Yes', 'No'], + }, + { + filed_label: 'Nama Item Bundling', + filter_column: 'tr_item_bundling__item_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Tipe Pelanggan', + filter_column: 'main__customer_type', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'No. PoS', + filter_column: 'main__creator_counter_no', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Tanggal Pengembalian', + filter_column: 'refund__refund_date', + field_type: FILTER_FIELD_TYPE.date_range_picker, + filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, + }, + { + filed_label: 'Kode Pengembalian', + filter_column: 'refund__code', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Nama Pelanggan', + filter_column: 'main__customer_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Bank/Issuer', + filter_column: 'main__payment_type_method_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + { + filed_label: 'Dibuat Oleh', + filter_column: 'main__creator_name', + field_type: FILTER_FIELD_TYPE.input_tag, + filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, + }, + ], +}; diff --git a/src/modules/reports/shared/configs/transaction-report/index.ts b/src/modules/reports/shared/configs/transaction-report/index.ts index 1f3b73a..c116baa 100644 --- a/src/modules/reports/shared/configs/transaction-report/index.ts +++ b/src/modules/reports/shared/configs/transaction-report/index.ts @@ -2,6 +2,7 @@ import { ReportConfigEntity } from '../../entities/report-config.entity'; import IncomeReport from './configs/income'; import IncomeReportPerItem from './configs/income-per-item'; +import IncomeReportPerItemMaster from './configs/income-per-item-master'; import GivingDiscount from './configs/giving-discounts'; import VisitorsPerRideReport from './configs/visitors-per-ride'; import TimePerRideReport from './configs/time-per-ride'; @@ -14,6 +15,7 @@ import ReconciliationReport from './configs/reconciliation'; export const TransactionReportConfig: ReportConfigEntity[] = [ IncomeReport, IncomeReportPerItem, + IncomeReportPerItemMaster, GivingDiscount, // VisitorsPerRideReport, // TimePerRideReport, diff --git a/src/modules/transaction/profit-share-formula/domain/usecases/managers/index-profit-share-formula.manager.ts b/src/modules/transaction/profit-share-formula/domain/usecases/managers/index-profit-share-formula.manager.ts new file mode 100644 index 0000000..f03482e --- /dev/null +++ b/src/modules/transaction/profit-share-formula/domain/usecases/managers/index-profit-share-formula.manager.ts @@ -0,0 +1,56 @@ +import { Injectable } from '@nestjs/common'; +import { + Param, + RelationParam, +} from 'src/core/modules/domain/entities/base-filter.entity'; +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 { BaseIndexManager } from 'src/core/modules/domain/usecase/managers/base-index.manager'; +import { SelectQueryBuilder } from 'typeorm'; + +@Injectable() +export class IndexProfitShareFormulaManager extends BaseIndexManager { + setQueryFilter( + queryBuilder: SelectQueryBuilder, + ): SelectQueryBuilder { + return queryBuilder; + } + + get specificFilter(): Param[] { + return [ + { + cols: `${this.tableName}.type::text`, + data: [FormulaType.PROFIT_SHARE], + }, + ]; + } + + 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 []; + } +} 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 053114a..7a22c6c 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 @@ -11,6 +11,7 @@ import { SalesPriceFormulaEntity } from 'src/modules/transaction/sales-price-for 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'; +import { FormulaType } from 'src/modules/transaction/sales-price-formula/constants'; @Injectable() export class UpdateProfitShareFormulaManager extends BaseUpdateManager { @@ -30,6 +31,35 @@ export class UpdateProfitShareFormulaManager extends BaseUpdateManager { + const additionalFormula = this.data.additional; + for (const additional of additionalFormula) { + /** + * Find formula for variable + * If the formula doesn't exist, then create data for save + */ + const formula = (await this.dataService.getOneByOptions({ + where: { + value_for: additional.value_for, + }, + })) ?? { + editor_id: this.user.id, + editor_name: this.user.name, + updated_at: new Date().getTime(), + created_at: new Date().getTime(), + type: FormulaType.PROFIT_SHARE, + }; + + // Update formula value to exist formula or new formula + formula.formula_render = additional.formula_render; + formula.formula_string = additional.formula_string; + formula.value_for = additional.value_for; + + /** + * This function is create, but inside function is save + * So, if the id is provide, the data will be update instead create new data + */ + this.dataService.create(null, null, formula); + } return; } diff --git a/src/modules/transaction/profit-share-formula/domain/usecases/profit-share-formula-read.orchestrator.ts b/src/modules/transaction/profit-share-formula/domain/usecases/profit-share-formula-read.orchestrator.ts index ef86f67..9dffdab 100644 --- a/src/modules/transaction/profit-share-formula/domain/usecases/profit-share-formula-read.orchestrator.ts +++ b/src/modules/transaction/profit-share-formula/domain/usecases/profit-share-formula-read.orchestrator.ts @@ -3,14 +3,24 @@ import { DetailProfitShareFormulaManager } from './managers/detail-profit-share- import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { SalesPriceFormulaReadService } from 'src/modules/transaction/sales-price-formula/data/services/sales-price-formula-read.service'; import { SalesPriceFormulaEntity } from 'src/modules/transaction/sales-price-formula/domain/entities/sales-price-formula.entity'; +import { IndexProfitShareFormulaManager } from './managers/index-profit-share-formula.manager'; @Injectable() export class ProfitShareFormulaReadOrchestrator { constructor( private detailManager: DetailProfitShareFormulaManager, + private indexManager: IndexProfitShareFormulaManager, private serviceData: SalesPriceFormulaReadService, ) {} + async index(): Promise { + this.indexManager.setFilterParam({}); + this.indexManager.setService(this.serviceData, TABLE_NAME.PRICE_FORMULA); + await this.indexManager.execute(); + const { data } = this.indexManager.getResult(); + return data; + } + async detail(): Promise { this.detailManager.setData(''); this.detailManager.setService(this.serviceData, TABLE_NAME.PRICE_FORMULA); diff --git a/src/modules/transaction/profit-share-formula/infrastructure/profit-share-formula-read.controller.ts b/src/modules/transaction/profit-share-formula/infrastructure/profit-share-formula-read.controller.ts index 7a12ffe..aa4c92d 100644 --- a/src/modules/transaction/profit-share-formula/infrastructure/profit-share-formula-read.controller.ts +++ b/src/modules/transaction/profit-share-formula/infrastructure/profit-share-formula-read.controller.ts @@ -1,4 +1,4 @@ -import { Controller, Get, Param } from '@nestjs/common'; +import { Controller, Get } from '@nestjs/common'; import { ProfitShareFormulaReadOrchestrator } from '../domain/usecases/profit-share-formula-read.orchestrator'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { Public } from 'src/core/guards'; @@ -15,4 +15,9 @@ export class ProfitShareFormulaReadController { async detail(): Promise { return await this.orchestrator.detail(); } + + @Get('detail') + async breakdown(): Promise { + return await this.orchestrator.index(); + } } 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 31ff6f2..51b8947 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 @@ -12,6 +12,7 @@ import { DetailProfitShareFormulaManager } from './domain/usecases/managers/deta 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'; +import { IndexProfitShareFormulaManager } from './domain/usecases/managers/index-profit-share-formula.manager'; @Module({ imports: [ @@ -29,6 +30,7 @@ import { TaxModel } from '../tax/data/models/tax.model'; providers: [ DetailProfitShareFormulaManager, UpdateProfitShareFormulaManager, + IndexProfitShareFormulaManager, ProfitShareFormulaDataOrchestrator, ProfitShareFormulaReadOrchestrator, diff --git a/src/modules/transaction/sales-price-formula/data/models/sales-price-formula.model.ts b/src/modules/transaction/sales-price-formula/data/models/sales-price-formula.model.ts index 4793e8b..9787733 100644 --- a/src/modules/transaction/sales-price-formula/data/models/sales-price-formula.model.ts +++ b/src/modules/transaction/sales-price-formula/data/models/sales-price-formula.model.ts @@ -22,6 +22,9 @@ export class SalesPriceFormulaModel @Column('varchar', { name: 'formula_string', nullable: true }) formula_string: string; + @Column('varchar', { default: 'dpp' }) + value_for: string; + @Column('varchar', { name: 'example_formula', nullable: true }) example_formula: string; 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 index c0adb62..aa57e47 100644 --- 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 @@ -6,5 +6,13 @@ export interface SalesPriceFormulaEntity extends BaseEntity { formula_string: string; // digunakan untuk menyimpan string dari formula example_formula: string; example_result: number; + value_for: string; type: FormulaType; + additional?: AdditionalFormula[]; +} + +export interface AdditionalFormula { + formula_render: any; + formula_string: string; + value_for: string; } diff --git a/src/modules/transaction/sales-price-formula/infrastructure/dto/sales-price-formula.dto.ts b/src/modules/transaction/sales-price-formula/infrastructure/dto/sales-price-formula.dto.ts index 4cc7542..6fab1d8 100644 --- a/src/modules/transaction/sales-price-formula/infrastructure/dto/sales-price-formula.dto.ts +++ b/src/modules/transaction/sales-price-formula/infrastructure/dto/sales-price-formula.dto.ts @@ -1,11 +1,37 @@ import { BaseDto } from 'src/core/modules/infrastructure/dto/base.dto'; -import { SalesPriceFormulaEntity } from '../../domain/entities/sales-price-formula.entity'; +import { + AdditionalFormula, + SalesPriceFormulaEntity, +} from '../../domain/entities/sales-price-formula.entity'; import { ApiProperty } from '@nestjs/swagger'; -import { ValidateIf } from 'class-validator'; +import { ValidateIf, ValidateNested } from 'class-validator'; import { Exclude } from 'class-transformer'; import { Any } from 'typeorm'; import { FormulaType } from '../../constants'; +export class AdditionalFormulaDto implements AdditionalFormula { + @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; + + @ApiProperty({ + type: String, + required: false, + }) + @ValidateIf((body) => body.value_for) + value_for: string; +} + export class SalesPriceFormulaDto extends BaseDto implements SalesPriceFormulaEntity @@ -30,6 +56,19 @@ export class SalesPriceFormulaDto @Exclude() example_result: number; + @Exclude() + value_for: string; + @Exclude() type: FormulaType; + + @ApiProperty({ + type: [AdditionalFormulaDto], + default: AdditionalFormulaDto, + }) + @ValidateIf(({ additional }) => { + return additional != null; + }) + @ValidateNested({ each: true }) + additional: AdditionalFormulaDto[]; }