Compare commits

...

32 Commits

Author SHA1 Message Date
irfan 2201071c68 Merge pull request 'feat: add column and filter payment reference at report income' (#139) from feat/report-income into production
Reviewed-on: #139
2025-05-27 11:36:38 +07:00
Firman Ramdhani 027025935c feat: add column and filter payment reference at report income 2025-05-19 17:42:35 +07:00
Firman Ramdhani 10cd1a711e feat: merubah tanggal pendapat di report pembalatan ke created_at 2025-05-16 18:37:40 +07:00
irfan bad4b2ea56 Merge pull request 'development' (#137) from development into production
Reviewed-on: #137
2025-05-09 23:33:05 +00:00
irfan 35919b91f7 Merge pull request 'refactor: replace CouchService with CouchModule in transaction modules' (#136) from development-local into development
Reviewed-on: #136
2025-05-09 23:32:40 +00:00
shancheas 8afbe33c01 refactor: replace CouchService with CouchModule in transaction modules 2025-05-10 06:31:57 +07:00
irfan f2d7ea80d2 Merge pull request 'development' (#135) from development into production
Reviewed-on: #135
2025-05-09 23:00:20 +00:00
irfan b18a834129 Merge pull request 'feat: enhance transaction calculations in CouchService and SalesPriceFormulaDataService' (#134) from development-local into development
Reviewed-on: #134
2025-05-09 22:59:35 +00:00
shancheas 928e2e7648 feat: enhance transaction calculations in CouchService and SalesPriceFormulaDataService 2025-05-09 17:56:30 +07:00
irfan 9b1f945873 Merge pull request 'development' (#133) from development into production
Reviewed-on: #133
2025-05-08 23:47:06 +00:00
irfan 6882314a9e Merge pull request 'development-local' (#132) from development-local into development
Reviewed-on: #132
2025-05-08 23:44:49 +00:00
shancheas 94baf956dd feat: add env to active skip transaction feature 2025-05-09 06:44:17 +07:00
shancheas e92f325807 fix: make transaction setting public 2025-05-02 11:25:33 +07:00
shancheas 66d76634b7 fix: find transactionSettingData with parameter 2025-05-02 09:02:45 +07:00
shancheas b91080906e feat: change save factor formula 2025-05-02 07:33:08 +07:00
shancheas 714b075e1d fix: add transaction setting api 2025-04-30 13:06:36 +07:00
shancheas da024606ff feat: add logic to skip save transaction 2025-04-30 07:46:19 +07:00
irfan 0b482be669 Merge pull request 'feat: add clear couch transaction API' (#131) from development into production
Reviewed-on: #131
2025-04-14 06:50:43 +00:00
shancheas 064112e731 feat: add clear couch transaction API 2025-04-14 12:06:05 +07:00
irfan 05473fce3d Merge pull request 'fix: time not null when save queue' (#130) from development into production
Reviewed-on: #130
2025-04-03 15:11:57 +00:00
shancheas d6a238a224 fix: time not null when save queue 2025-04-03 22:11:19 +07:00
irfan a8b3f67d1b Merge pull request 'development' (#129) from development into production
Reviewed-on: #129
2025-04-03 14:55:42 +00:00
shancheas dc5e938f75 feat: add configuration to show estimation queue time 2025-04-03 21:54:35 +07:00
shancheas eb4da7ccc4 temp: remove multiple admin login validation from queue admin 2025-04-03 21:54:04 +07:00
irfan bef9f99f13 Merge pull request 'fix: add error log to whatsapp service' (#128) from development into production
Reviewed-on: #128
2025-01-30 00:02:59 +00:00
shancheas 46caaba6bd fix: add error log to whatsapp service 2025-01-30 07:01:52 +07:00
irfan 8e0c8462b9 Merge pull request 'fix: throw error when display id is undefined' (#127) from development into production
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
Reviewed-on: #127
2025-01-23 15:15:43 +00:00
shancheas 7953c7dbbd fix: validate display uuid
continuous-integration/drone/push Build is passing Details
2025-01-23 22:15:26 +07:00
shancheas 6911f6f0a2 fix: don't send whatsapp notification when phone is null
continuous-integration/drone/push Build is passing Details
2025-01-23 22:11:15 +07:00
shancheas 762340a72b fix: throw error when display id is undefined
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2025-01-23 21:49:33 +07:00
irfan 00e2b6e015 Merge pull request 'feat: add text to speech api' (#126) from development into production
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
Reviewed-on: #126
2025-01-17 11:04:20 +00:00
shancheas 61cbbf81ef feat: add text to speech api
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
2025-01-16 12:12:26 +07:00
29 changed files with 1040 additions and 54 deletions

View File

@ -49,6 +49,7 @@
"exceljs": "^4.4.0", "exceljs": "^4.4.0",
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"googleapis": "^140.0.0", "googleapis": "^140.0.0",
"gtts": "^0.2.1",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"mathjs": "^13.0.2", "mathjs": "^13.0.2",
"midtrans-client": "^1.3.1", "midtrans-client": "^1.3.1",

View File

@ -33,7 +33,10 @@ import { SeasonTypeModel } from './modules/season-related/season-type/data/model
import { TaxModule } from './modules/transaction/tax/tax.module'; import { TaxModule } from './modules/transaction/tax/tax.module';
import { TaxModel } from './modules/transaction/tax/data/models/tax.model'; import { TaxModel } from './modules/transaction/tax/data/models/tax.model';
import { SalesPriceFormulaModule } from './modules/transaction/sales-price-formula/sales-price-formula.module'; 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'; import {
SalesPriceFormulaModel,
TransactionSettingModel,
} from './modules/transaction/sales-price-formula/data/models/sales-price-formula.model';
import { ProfitShareFormulaModule } from './modules/transaction/profit-share-formula/profit-share-formula.module'; import { ProfitShareFormulaModule } from './modules/transaction/profit-share-formula/profit-share-formula.module';
import { PaymentMethodModule } from './modules/transaction/payment-method/payment-method.module'; import { PaymentMethodModule } from './modules/transaction/payment-method/payment-method.module';
import { PaymentMethodModel } from './modules/transaction/payment-method/data/models/payment-method.model'; import { PaymentMethodModel } from './modules/transaction/payment-method/data/models/payment-method.model';
@ -137,6 +140,7 @@ import { QueueBucketModel } from './modules/queue/data/models/queue-bucket.model
TransactionItemBreakdownModel, TransactionItemBreakdownModel,
TransactionItemTaxModel, TransactionItemTaxModel,
TransactionBreakdownTaxModel, TransactionBreakdownTaxModel,
TransactionSettingModel,
UserModel, UserModel,
UserLoginModel, UserLoginModel,

View File

@ -12,6 +12,7 @@ export enum TABLE_NAME {
NEWS = 'news', NEWS = 'news',
PAYMENT_METHOD = 'payment_methods', PAYMENT_METHOD = 'payment_methods',
PRICE_FORMULA = 'price_formulas', PRICE_FORMULA = 'price_formulas',
TRANSACTION_SETTING = 'transaction_settings',
REFUND = 'refunds', REFUND = 'refunds',
REFUND_ITEM = 'refund_items', REFUND_ITEM = 'refund_items',
SEASON_TYPE = 'season_types', SEASON_TYPE = 'season_types',

View File

@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class TransactionSettingModel1745991391299
implements MigrationInterface
{
name = 'TransactionSettingModel1745991391299';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "transaction_settings" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "creator_id" character varying(36), "creator_name" character varying(125), "editor_id" character varying(36), "editor_name" character varying(125), "created_at" bigint NOT NULL, "updated_at" bigint NOT NULL, "value" numeric NOT NULL DEFAULT '100', CONSTRAINT "PK_db7fb38a439358b499ebdee4761" PRIMARY KEY ("id"))`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "transaction_settings"`);
}
}

View File

@ -73,13 +73,14 @@ export class LoginAdminQueueManager extends BaseCustomManager<UserEntity> {
message: `Akun anda sudah login di item "${hasLoginAsQueue?.item_name}"`, message: `Akun anda sudah login di item "${hasLoginAsQueue?.item_name}"`,
error: 'Unauthorized', error: 'Unauthorized',
}); });
} else if (itemLogin && itemLogin.user_id !== this.userLogin.id) {
throw new UnauthorizedException({
statusCode: HttpStatus.UNAUTHORIZED,
message: `"${userLoginItem.name}" masih login sebagai admin antrian `,
error: 'Unauthorized',
});
} }
// else if (itemLogin && itemLogin.user_id !== this.userLogin.id) {
// throw new UnauthorizedException({
// statusCode: HttpStatus.UNAUTHORIZED,
// message: `"${userLoginItem.name}" masih login sebagai admin antrian `,
// error: 'Unauthorized',
// });
// }
// * Disini untuk isi token // * Disini untuk isi token
const tokenData = { const tokenData = {

View File

@ -19,6 +19,7 @@ export class CouchService {
} }
async onModuleInit() { async onModuleInit() {
// return;
const nano = this.nanoInstance; const nano = this.nanoInstance;
for (const database of DatabaseListen) { for (const database of DatabaseListen) {
const db = nano.db.use(database); const db = nano.db.use(database);
@ -95,4 +96,75 @@ export class CouchService {
return null; return null;
} }
} }
public async totalTodayTransactions(database = 'transaction') {
try {
const nano = this.nanoInstance;
const db = nano.use<any>(database);
// Get today's start timestamp (midnight)
const today = new Date();
today.setHours(0, 0, 0, 0);
const todayTimestamp = today.getTime();
// Query for documents created today
const selector = {
created_at: {
$gte: todayTimestamp,
},
};
const result = await db.find({
selector: selector,
fields: ['_id', 'payment_total_pay'],
limit: 10000,
});
return result.docs.reduce(
(sum, doc) => sum + (doc.payment_total_pay || 0),
0,
);
} catch (error) {
console.log(error);
apm.captureError(error);
return 0;
}
}
getUnixTimestampLast7Days() {
const date = new Date();
date.setDate(date.getDate() - 4);
date.setHours(0, 0, 0, 0);
return date.getTime();
}
public async clearTransactions() {
const nano = this.nanoInstance;
const transaction = nano.use('transaction');
const expiredDate = this.getUnixTimestampLast7Days();
const selectorPayment = {
created_at: {
$lt: expiredDate,
},
};
const transactions = await transaction.find({
selector: selectorPayment,
fields: ['_id', '_rev'],
limit: 100000,
});
const { docs } = transactions;
console.log(docs.length);
const deletedDocs = {
docs: docs.map((doc) => ({
_id: doc._id,
_rev: doc._rev,
_deleted: true,
})),
};
await transaction.bulk(deletedDocs);
}
} }

View File

@ -5,13 +5,17 @@ import { Public } from 'src/core/guards';
import * as Nano from 'nano'; import * as Nano from 'nano';
import { CreateUserPrivilegeDto } from 'src/modules/user-related/user-privilege/infrastructure/dto/create-user-privilege.dto'; import { CreateUserPrivilegeDto } from 'src/modules/user-related/user-privilege/infrastructure/dto/create-user-privilege.dto';
import { ConfigService } from '@nestjs/config'; import { ConfigService } from '@nestjs/config';
import { CouchService } from '../data/services/couch.service';
@ApiTags(`couch`) @ApiTags(`couch`)
@Controller('v1/couch') @Controller('v1/couch')
@Public() @Public()
@Injectable() @Injectable()
export class CouchDataController { export class CouchDataController {
constructor(private configService: ConfigService) {} constructor(
private configService: ConfigService,
private couchService: CouchService,
) {}
get nanoInstance() { get nanoInstance() {
const couchConfiguration = this.configService.get<string>('COUCHDB_CONFIG'); const couchConfiguration = this.configService.get<string>('COUCHDB_CONFIG');
@ -64,4 +68,11 @@ export class CouchDataController {
// return people.get(); // return people.get();
} catch (error) {} } catch (error) {}
} }
@Public(true)
@Get('clear-transactions')
async clearTransactions(): Promise<string> {
await this.couchService.clearTransactions();
return 'OK';
}
} }

View File

@ -1,4 +1,10 @@
import { Controller, Get, Param, Query } from '@nestjs/common'; import {
Controller,
Get,
Param,
Query,
UnauthorizedException,
} from '@nestjs/common';
import { FilterItemQueueDto } from './dto/filter-item-queue.dto'; import { FilterItemQueueDto } from './dto/filter-item-queue.dto';
import { Pagination } from 'src/core/response'; import { Pagination } from 'src/core/response';
import { PaginationResponse } from 'src/core/response/domain/ok-response.interface'; import { PaginationResponse } from 'src/core/response/domain/ok-response.interface';
@ -7,6 +13,7 @@ import { ItemQueueReadOrchestrator } from '../domain/usecases/item-queue-read.or
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; import { MODULE_NAME } from 'src/core/strings/constants/module.constants';
import { Public } from 'src/core/guards'; import { Public } from 'src/core/guards';
import { validate as isValidUUID } from 'uuid';
@ApiTags(`${MODULE_NAME.ITEM_QUEUE.split('-').join(' ')} - read`) @ApiTags(`${MODULE_NAME.ITEM_QUEUE.split('-').join(' ')} - read`)
@Controller(`v1/${MODULE_NAME.ITEM_QUEUE}`) @Controller(`v1/${MODULE_NAME.ITEM_QUEUE}`)
@ -44,6 +51,7 @@ export class ItemQueueReadController {
@Get('display/:id') @Get('display/:id')
@Public(true) @Public(true)
async detailPublic(@Param('id') id: string): Promise<ItemQueueEntity> { async detailPublic(@Param('id') id: string): Promise<ItemQueueEntity> {
if (!isValidUUID(id)) throw new UnauthorizedException('id is required');
return await this.orchestrator.detail(id); return await this.orchestrator.detail(id);
} }
} }

View File

@ -87,7 +87,10 @@ export class QueueAdminOrchestrator {
// }, // },
// ]); // ]);
if (queueTicket.last_notification < currentTime - call_preparation) { if (
queueTicket.item.ticket.phone != null &&
queueTicket.last_notification < currentTime - call_preparation
) {
await notification.queueProcess(payload); await notification.queueProcess(payload);
this.service.updateLastNotification(queueId, currentTime); this.service.updateLastNotification(queueId, currentTime);
} }

View File

@ -153,6 +153,9 @@ export class GenerateQueueManager {
registerQueueManager.setService(this.queueService, TABLE_NAME.QUEUE); registerQueueManager.setService(this.queueService, TABLE_NAME.QUEUE);
await registerQueueManager.execute(); await registerQueueManager.execute();
return registerQueueManager.getResult(); const result = await registerQueueManager.getResult();
result.time = null;
return result;
} }
} }

View File

@ -1,4 +1,4 @@
import { Body, Controller, Get, Param, Post } from '@nestjs/common'; import { Body, Controller, Get, Param, Post, Query, Res } from '@nestjs/common';
import { MODULE_NAME } from 'src/core/strings/constants/module.constants'; import { MODULE_NAME } from 'src/core/strings/constants/module.constants';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
@ -15,6 +15,9 @@ import { mappingRevertTransaction } from 'src/modules/transaction/transaction/do
import { TransactionType } from 'src/modules/transaction/transaction/constants'; import { TransactionType } from 'src/modules/transaction/transaction/constants';
import { QueueModel } from '../../data/models/queue.model'; import { QueueModel } from '../../data/models/queue.model';
import * as Gtts from 'gtts';
import { Response } from 'express';
@ApiTags(`Queue`) @ApiTags(`Queue`)
@Controller(`v1/${MODULE_NAME.QUEUE}`) @Controller(`v1/${MODULE_NAME.QUEUE}`)
@Public(true) @Public(true)
@ -105,6 +108,15 @@ export class QueueController {
return await this.orchestrator.queueOrderItems(id); return await this.orchestrator.queueOrderItems(id);
} }
@Get('speech')
async speech(
@Query('text') text: string,
@Res() response: Response,
): Promise<void> {
const gtts = new Gtts(text, 'id');
gtts.stream().pipe(response);
}
@Get(':id/items/:item_id') @Get(':id/items/:item_id')
async queueItemTickets( async queueItemTickets(
@Param('id') id: string, @Param('id') id: string,

View File

@ -42,9 +42,16 @@ export default <ReportConfigEntity>{
}, },
column_configs: [ column_configs: [
// {
// column: 'main__payment_date',
// query: `to_char(main.payment_date, 'DD-MM-YYYY')`,
// label: 'Tgl. Pendapatan',
// type: DATA_TYPE.DIMENSION,
// format: DATA_FORMAT.TEXT,
// },
{ {
column: 'main__payment_date', column: 'main__payment_date',
query: `to_char(main.payment_date, 'DD-MM-YYYY')`, query: `to_char(cast(to_timestamp(main.created_at/1000) as date),'DD-MM-YYYY')`,
label: 'Tgl. Pendapatan', label: 'Tgl. Pendapatan',
type: DATA_TYPE.DIMENSION, type: DATA_TYPE.DIMENSION,
format: DATA_FORMAT.TEXT, format: DATA_FORMAT.TEXT,
@ -282,15 +289,22 @@ export default <ReportConfigEntity>{
}, },
], ],
filter_configs: [ filter_configs: [
// {
// filed_label: 'Tgl. Pembatalan',
// filter_column: 'main__payment_date',
// field_type: FILTER_FIELD_TYPE.date_range_picker,
// filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP,
// // date_format: 'DD-MM-YYYY',
// date_format: 'YYYY-MM-DD',
// },
{ {
filed_label: 'Tgl. Pembatalan', filed_label: 'Tgl. Pembatalan',
filter_column: 'main__payment_date', filter_column: 'main__payment_date',
field_type: FILTER_FIELD_TYPE.date_range_picker, field_type: FILTER_FIELD_TYPE.date_range_picker,
filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP, filter_type: FILTER_TYPE.DATE_IN_RANGE_TIMESTAMP,
// date_format: 'DD-MM-YYYY', date_format: 'DD-MM-YYYY',
date_format: 'YYYY-MM-DD',
}, },
{ {
filed_label: 'Sumber', filed_label: 'Sumber',
filter_column: 'main__type', filter_column: 'main__type',
@ -392,8 +406,9 @@ export default <ReportConfigEntity>{
}, },
], ],
customQueryColumn(column) { customQueryColumn(column) {
if (column === 'main__payment_date') return 'main.payment_date'; // if (column === 'main__payment_date') return 'main.payment_date';
else if (column === 'refund__refund_date') return 'refund.refund_date'; // else if (column === 'refund__refund_date') return 'refund.refund_date';
if (column === 'refund__refund_date') return 'refund.refund_date';
return; return;
}, },
}; };

View File

@ -255,7 +255,14 @@ export default <ReportConfigEntity>{
{ {
column: 'main__payment_card_information', column: 'main__payment_card_information',
query: 'main.payment_card_information', query: 'main.payment_card_information',
label: 'Information', label: 'Card Information',
type: DATA_TYPE.DIMENSION,
format: DATA_FORMAT.TEXT,
},
{
column: 'main__payment_code_reference',
query: 'main.payment_code_reference',
label: 'Payment Reference',
type: DATA_TYPE.DIMENSION, type: DATA_TYPE.DIMENSION,
format: DATA_FORMAT.TEXT, format: DATA_FORMAT.TEXT,
}, },
@ -334,6 +341,18 @@ export default <ReportConfigEntity>{
field_type: FILTER_FIELD_TYPE.input_tag, field_type: FILTER_FIELD_TYPE.input_tag,
filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS, filter_type: FILTER_TYPE.TEXT_MULTIPLE_CONTAINS,
}, },
{
filed_label: 'Card Information',
filter_column: 'main__payment_card_information',
field_type: FILTER_FIELD_TYPE.input_tag,
filter_type: FILTER_TYPE.TEXT_IN_MEMBER,
},
{
filed_label: 'Payment Reference',
filter_column: 'main__payment_code_reference',
field_type: FILTER_FIELD_TYPE.input_tag,
filter_type: FILTER_TYPE.TEXT_IN_MEMBER,
},
{ {
filed_label: 'Tgl. Pengembalian', filed_label: 'Tgl. Pengembalian',
filter_column: 'refund__refund_date', filter_column: 'refund__refund_date',

View File

@ -1,5 +1,8 @@
import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import { SalesPriceFormulaEntity } from '../../domain/entities/sales-price-formula.entity'; import {
SalesPriceFormulaEntity,
TransactionSetting,
} from '../../domain/entities/sales-price-formula.entity';
import { Column, Entity } from 'typeorm'; import { Column, Entity } from 'typeorm';
import { BaseModel } from 'src/core/modules/data/model/base.model'; import { BaseModel } from 'src/core/modules/data/model/base.model';
import { FormulaType } from '../../constants'; import { FormulaType } from '../../constants';
@ -31,3 +34,12 @@ export class SalesPriceFormulaModel
@Column('numeric', { name: 'example_result', nullable: true }) @Column('numeric', { name: 'example_result', nullable: true })
example_result: number; example_result: number;
} }
@Entity(TABLE_NAME.TRANSACTION_SETTING)
export class TransactionSettingModel
extends BaseModel<TransactionSetting>
implements TransactionSetting
{
@Column('numeric', { default: 100 })
value: number;
}

View File

@ -1,13 +1,26 @@
import { Injectable, UnprocessableEntityException } from '@nestjs/common'; import {
Injectable,
Logger,
UnprocessableEntityException,
} from '@nestjs/common';
import { BaseDataService } from 'src/core/modules/data/service/base-data.service'; import { BaseDataService } from 'src/core/modules/data/service/base-data.service';
import { SalesPriceFormulaEntity } from '../../domain/entities/sales-price-formula.entity'; import {
SalesPriceFormulaEntity,
TransactionSetting,
} from '../../domain/entities/sales-price-formula.entity';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { SalesPriceFormulaModel } from '../models/sales-price-formula.model'; import {
SalesPriceFormulaModel,
TransactionSettingModel,
} from '../models/sales-price-formula.model';
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { FormulaType } from '../../constants'; import { FormulaType } from '../../constants';
import { TaxModel } from 'src/modules/transaction/tax/data/models/tax.model'; import { TaxModel } from 'src/modules/transaction/tax/data/models/tax.model';
import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'; import { ItemModel } from 'src/modules/item-related/item/data/models/item.model';
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
import { CouchService } from 'src/modules/configuration/couch/data/services/couch.service';
import { TransactionType } from 'src/modules/transaction/transaction/constants';
@Injectable() @Injectable()
export class SalesPriceFormulaDataService extends BaseDataService<SalesPriceFormulaEntity> { export class SalesPriceFormulaDataService extends BaseDataService<SalesPriceFormulaEntity> {
@ -18,6 +31,11 @@ export class SalesPriceFormulaDataService extends BaseDataService<SalesPriceForm
private tax: Repository<TaxModel>, private tax: Repository<TaxModel>,
@InjectRepository(ItemModel, CONNECTION_NAME.DEFAULT) @InjectRepository(ItemModel, CONNECTION_NAME.DEFAULT)
private item: Repository<ItemModel>, private item: Repository<ItemModel>,
@InjectRepository(TransactionSettingModel, CONNECTION_NAME.DEFAULT)
private transactionSetting: Repository<TransactionSettingModel>,
@InjectRepository(TransactionModel, CONNECTION_NAME.DEFAULT)
private transaction: Repository<TransactionModel>,
private couchService: CouchService,
) { ) {
super(repo); super(repo);
} }
@ -30,6 +48,47 @@ export class SalesPriceFormulaDataService extends BaseDataService<SalesPriceForm
}); });
} }
async sentToBlackHole() {
const transactionSettingData = await this.transactionSetting.findOne({
where: {},
});
const percentage = transactionSettingData?.value ?? 100;
// const transactionPercentage = Math.floor(Math.random() * 100) + 1;
const transactionPercentage = await this.dataSaveFactor();
Logger.log(`Factor ${transactionPercentage} from ${percentage}`);
return transactionPercentage > percentage;
}
async dataSaveFactor() {
const today = new Date();
today.setHours(0, 0, 0, 0);
const todayTimestamp = today.getTime();
const totalTransactions = parseInt(
await this.transaction
.createQueryBuilder('transaction')
.select('SUM(transaction.payment_total_pay)', 'sum')
.where('transaction.created_at > :timestamp', {
timestamp: todayTimestamp,
})
.andWhere('transaction.type = :type', { type: TransactionType.COUNTER })
.getRawOne()
.then((result) => result.sum || 0),
);
const couchTransaction = await this.couchService.totalTodayTransactions();
if (couchTransaction == 0) return 0;
const factor = (totalTransactions / couchTransaction) * 100;
return factor;
}
async itemTax(id: string) { async itemTax(id: string) {
const item = await this.item.findOne({ const item = await this.item.findOne({
relations: ['tenant'], relations: ['tenant'],
@ -90,3 +149,13 @@ export class SalesPriceFormulaDataService extends BaseDataService<SalesPriceForm
return salesFormula; return salesFormula;
} }
} }
@Injectable()
export class TransactionSettingDataService extends BaseDataService<TransactionSetting> {
constructor(
@InjectRepository(TransactionSettingModel, CONNECTION_NAME.DEFAULT)
private repo: Repository<TransactionSettingModel>,
) {
super(repo);
}
}

View File

@ -1,7 +1,10 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { SalesPriceFormulaEntity } from '../../domain/entities/sales-price-formula.entity'; import { SalesPriceFormulaEntity } from '../../domain/entities/sales-price-formula.entity';
import { InjectRepository } from '@nestjs/typeorm'; import { InjectRepository } from '@nestjs/typeorm';
import { SalesPriceFormulaModel } from '../models/sales-price-formula.model'; import {
SalesPriceFormulaModel,
TransactionSettingModel,
} from '../models/sales-price-formula.model';
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import { Repository } from 'typeorm'; import { Repository } from 'typeorm';
import { BaseReadService } from 'src/core/modules/data/service/base-read.service'; import { BaseReadService } from 'src/core/modules/data/service/base-read.service';
@ -11,7 +14,25 @@ export class SalesPriceFormulaReadService extends BaseReadService<SalesPriceForm
constructor( constructor(
@InjectRepository(SalesPriceFormulaModel, CONNECTION_NAME.DEFAULT) @InjectRepository(SalesPriceFormulaModel, CONNECTION_NAME.DEFAULT)
private repo: Repository<SalesPriceFormulaModel>, private repo: Repository<SalesPriceFormulaModel>,
@InjectRepository(TransactionSettingModel, CONNECTION_NAME.DEFAULT)
private transactionSetting: Repository<TransactionSettingModel>,
) { ) {
super(repo); super(repo);
} }
async getTransactionSetting(): Promise<any> {
try {
const data = await this.transactionSetting.findOne({
where: {},
order: {
created_at: 'DESC',
},
});
return data;
} catch (error) {
throw error;
}
}
} }

View File

@ -1,5 +1,6 @@
import { BaseEntity } from 'src/core/modules/domain/entities/base.entity'; import { BaseEntity } from 'src/core/modules/domain/entities/base.entity';
import { FormulaType } from '../../constants'; import { FormulaType } from '../../constants';
import { BaseCoreEntity } from 'src/core/modules/domain/entities/base-core.entity';
export interface SalesPriceFormulaEntity extends BaseEntity { export interface SalesPriceFormulaEntity extends BaseEntity {
formula_render: any; // json type formula_render: any; // json type
@ -16,3 +17,7 @@ export interface AdditionalFormula {
formula_string: string; formula_string: string;
value_for: string; value_for: string;
} }
export interface TransactionSetting extends BaseCoreEntity {
value: number;
}

View File

@ -0,0 +1,40 @@
import { Injectable } from '@nestjs/common';
import { BaseUpdateManager } from 'src/core/modules/domain/usecase/managers/base-update.manager';
import { TransactionSetting } from '../../entities/sales-price-formula.entity';
import { TransactionSettingModel } from '../../../data/models/sales-price-formula.model';
import {
EventTopics,
columnUniques,
validateRelations,
} from 'src/core/strings/constants/interface.constants';
@Injectable()
export class UpdateTransactionSettingManager extends BaseUpdateManager<TransactionSetting> {
async validateProcess(): Promise<void> {
return;
}
async beforeProcess(): Promise<void> {
return;
}
async afterProcess(): Promise<void> {
return;
}
get validateRelations(): validateRelations[] {
return [];
}
get uniqueColumns(): columnUniques[] {
return [];
}
get entityTarget(): any {
return TransactionSettingModel;
}
get eventTopics(): EventTopics[] {
return [];
}
}

View File

@ -1,10 +1,17 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { SalesPriceFormulaDataService } from '../../data/services/sales-price-formula-data.service'; import {
import { SalesPriceFormulaEntity } from '../entities/sales-price-formula.entity'; SalesPriceFormulaDataService,
TransactionSettingDataService,
} from '../../data/services/sales-price-formula-data.service';
import {
SalesPriceFormulaEntity,
TransactionSetting,
} from '../entities/sales-price-formula.entity';
import { UpdateSalesPriceFormulaManager } from './managers/update-sales-price-formula.manager'; import { UpdateSalesPriceFormulaManager } from './managers/update-sales-price-formula.manager';
import { TABLE_NAME } from 'src/core/strings/constants/table.constants'; import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import { FormulaType } from '../../constants'; import { FormulaType } from '../../constants';
import { TaxDataService } from 'src/modules/transaction/tax/data/services/tax-data.service'; import { TaxDataService } from 'src/modules/transaction/tax/data/services/tax-data.service';
import { UpdateTransactionSettingManager } from './managers/update-transaction-setting.manager';
@Injectable() @Injectable()
export class SalesPriceFormulaDataOrchestrator { export class SalesPriceFormulaDataOrchestrator {
@ -12,6 +19,8 @@ export class SalesPriceFormulaDataOrchestrator {
private updateManager: UpdateSalesPriceFormulaManager, private updateManager: UpdateSalesPriceFormulaManager,
private serviceData: SalesPriceFormulaDataService, private serviceData: SalesPriceFormulaDataService,
private taxService: TaxDataService, private taxService: TaxDataService,
private transactionSettingService: TransactionSettingDataService,
private transactionSettingManager: UpdateTransactionSettingManager,
) {} ) {}
async update(data): Promise<SalesPriceFormulaEntity> { async update(data): Promise<SalesPriceFormulaEntity> {
@ -30,4 +39,14 @@ export class SalesPriceFormulaDataOrchestrator {
await this.updateManager.execute(); await this.updateManager.execute();
return this.updateManager.getResult(); return this.updateManager.getResult();
} }
async updateTransactionSetting(data): Promise<TransactionSetting> {
this.transactionSettingManager.setData(data.id, data);
this.transactionSettingManager.setService(
this.transactionSettingService,
TABLE_NAME.TRANSACTION_SETTING,
);
await this.transactionSettingManager.execute();
return this.transactionSettingManager.getResult();
}
} }

View File

@ -17,4 +17,12 @@ export class SalesPriceFormulaReadOrchestrator {
await this.detailManager.execute(); await this.detailManager.execute();
return this.detailManager.getResult(); return this.detailManager.getResult();
} }
async getTransactionSetting(): Promise<any> {
try {
return await this.serviceData.getTransactionSetting();
} catch (error) {
throw error;
}
}
} }

View File

@ -2,6 +2,7 @@ import { BaseDto } from 'src/core/modules/infrastructure/dto/base.dto';
import { import {
AdditionalFormula, AdditionalFormula,
SalesPriceFormulaEntity, SalesPriceFormulaEntity,
TransactionSetting,
} from '../../domain/entities/sales-price-formula.entity'; } from '../../domain/entities/sales-price-formula.entity';
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { ValidateIf, ValidateNested } from 'class-validator'; import { ValidateIf, ValidateNested } from 'class-validator';
@ -32,6 +33,21 @@ export class AdditionalFormulaDto implements AdditionalFormula {
value_for: string; value_for: string;
} }
export class TransactionSettingDto implements TransactionSetting {
@ApiProperty({
type: String,
required: true,
})
@ValidateIf((body) => body.id)
id?: string;
@ApiProperty({
type: Number,
required: true,
})
value: number;
}
export class SalesPriceFormulaDto export class SalesPriceFormulaDto
extends BaseDto extends BaseDto
implements SalesPriceFormulaEntity implements SalesPriceFormulaEntity

View File

@ -1,12 +1,18 @@
import { Body, Controller, Post } from '@nestjs/common'; import { Body, Controller, Post, Put } from '@nestjs/common';
import { SalesPriceFormulaDataOrchestrator } from '../domain/usecases/sales-price-formula-data.orchestrator'; import { SalesPriceFormulaDataOrchestrator } from '../domain/usecases/sales-price-formula-data.orchestrator';
import { SalesPriceFormulaDto } from './dto/sales-price-formula.dto'; import {
SalesPriceFormulaDto,
TransactionSettingDto,
} from './dto/sales-price-formula.dto';
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { SalesPriceFormulaEntity } from '../domain/entities/sales-price-formula.entity'; import {
SalesPriceFormulaEntity,
TransactionSetting,
} from '../domain/entities/sales-price-formula.entity';
import { Public } from 'src/core/guards'; import { Public } from 'src/core/guards';
@ApiTags(`sales price formulas - data`) @ApiTags(`sales price formulas - data`)
@Controller('v1/sales-price-formula') @Controller(['v1/sales-price-formula', 'v1/transaction-setting'])
@Public(false) @Public(false)
@ApiBearerAuth('JWT') @ApiBearerAuth('JWT')
export class SalesPriceFormulaDataController { export class SalesPriceFormulaDataController {
@ -18,4 +24,12 @@ export class SalesPriceFormulaDataController {
): Promise<SalesPriceFormulaEntity> { ): Promise<SalesPriceFormulaEntity> {
return await this.orchestrator.update(data); return await this.orchestrator.update(data);
} }
@Public(true)
@Put()
async updateTransactionSetting(
@Body() data: TransactionSettingDto,
): Promise<TransactionSetting> {
return await this.orchestrator.updateTransactionSetting(data);
}
} }

View File

@ -5,7 +5,7 @@ import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
import { Public } from 'src/core/guards'; import { Public } from 'src/core/guards';
@ApiTags(`sales price formulas - read`) @ApiTags(`sales price formulas - read`)
@Controller('v1/sales-price-formula') @Controller(['v1/sales-price-formula', 'v1/transaction-setting'])
@Public(false) @Public(false)
@ApiBearerAuth('JWT') @ApiBearerAuth('JWT')
export class SalesPriceFormulaReadController { export class SalesPriceFormulaReadController {
@ -15,4 +15,10 @@ export class SalesPriceFormulaReadController {
async detail(): Promise<SalesPriceFormulaEntity> { async detail(): Promise<SalesPriceFormulaEntity> {
return await this.orchestrator.detail(); return await this.orchestrator.detail();
} }
@Public(true)
@Get('detail')
async getTransactionSetting(): Promise<any> {
return await this.orchestrator.getTransactionSetting();
}
} }

View File

@ -2,7 +2,10 @@ import { Global, Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config'; import { ConfigModule } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants'; import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import { SalesPriceFormulaDataService } from './data/services/sales-price-formula-data.service'; import {
SalesPriceFormulaDataService,
TransactionSettingDataService,
} from './data/services/sales-price-formula-data.service';
import { SalesPriceFormulaReadService } from './data/services/sales-price-formula-read.service'; import { SalesPriceFormulaReadService } from './data/services/sales-price-formula-read.service';
import { SalesPriceFormulaReadController } from './infrastructure/sales-price-formula-read.controller'; import { SalesPriceFormulaReadController } from './infrastructure/sales-price-formula-read.controller';
import { SalesPriceFormulaReadOrchestrator } from './domain/usecases/sales-price-formula-read.orchestrator'; import { SalesPriceFormulaReadOrchestrator } from './domain/usecases/sales-price-formula-read.orchestrator';
@ -11,20 +14,33 @@ import { SalesPriceFormulaDataOrchestrator } from './domain/usecases/sales-price
import { CqrsModule } from '@nestjs/cqrs'; import { CqrsModule } from '@nestjs/cqrs';
import { UpdateSalesPriceFormulaManager } from './domain/usecases/managers/update-sales-price-formula.manager'; import { UpdateSalesPriceFormulaManager } from './domain/usecases/managers/update-sales-price-formula.manager';
import { DetailSalesPriceFormulaManager } from './domain/usecases/managers/detail-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 {
SalesPriceFormulaModel,
TransactionSettingModel,
} from './data/models/sales-price-formula.model';
import { TaxDataService } from '../tax/data/services/tax-data.service'; import { TaxDataService } from '../tax/data/services/tax-data.service';
import { TaxModel } from '../tax/data/models/tax.model'; import { TaxModel } from '../tax/data/models/tax.model';
import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'; import { ItemModel } from 'src/modules/item-related/item/data/models/item.model';
import { UpdateTransactionSettingManager } from './domain/usecases/managers/update-transaction-setting.manager';
import { TransactionModel } from '../transaction/data/models/transaction.model';
import { CouchModule } from 'src/modules/configuration/couch/couch.module';
@Global() @Global()
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot(), ConfigModule.forRoot(),
TypeOrmModule.forFeature( TypeOrmModule.forFeature(
[SalesPriceFormulaModel, TaxModel, ItemModel], [
SalesPriceFormulaModel,
TransactionSettingModel,
TransactionModel,
TaxModel,
ItemModel,
],
CONNECTION_NAME.DEFAULT, CONNECTION_NAME.DEFAULT,
), ),
CqrsModule, CqrsModule,
CouchModule,
], ],
controllers: [ controllers: [
SalesPriceFormulaDataController, SalesPriceFormulaDataController,
@ -33,10 +49,12 @@ import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'
providers: [ providers: [
DetailSalesPriceFormulaManager, DetailSalesPriceFormulaManager,
UpdateSalesPriceFormulaManager, UpdateSalesPriceFormulaManager,
UpdateTransactionSettingManager,
TaxDataService, TaxDataService,
SalesPriceFormulaDataService, SalesPriceFormulaDataService,
SalesPriceFormulaReadService, SalesPriceFormulaReadService,
TransactionSettingDataService,
SalesPriceFormulaDataOrchestrator, SalesPriceFormulaDataOrchestrator,
SalesPriceFormulaReadOrchestrator, SalesPriceFormulaReadOrchestrator,

View File

@ -23,14 +23,17 @@ import { BatchConfirmTaxManager } from './domain/usecases/managers/batch-confirm
import { BatchInactiveTaxManager } from './domain/usecases/managers/batch-inactive-tax.manager'; import { BatchInactiveTaxManager } from './domain/usecases/managers/batch-inactive-tax.manager';
import { TaxModel } from './data/models/tax.model'; import { TaxModel } from './data/models/tax.model';
import { SalesPriceFormulaReadService } from '../sales-price-formula/data/services/sales-price-formula-read.service'; import { SalesPriceFormulaReadService } from '../sales-price-formula/data/services/sales-price-formula-read.service';
import { SalesPriceFormulaModel } from '../sales-price-formula/data/models/sales-price-formula.model'; import {
SalesPriceFormulaModel,
TransactionSettingModel,
} from '../sales-price-formula/data/models/sales-price-formula.model';
import { IndexTaxFormulaManager } from './domain/usecases/managers/index-tax-formula.manager'; import { IndexTaxFormulaManager } from './domain/usecases/managers/index-tax-formula.manager';
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot(), ConfigModule.forRoot(),
TypeOrmModule.forFeature( TypeOrmModule.forFeature(
[TaxModel, SalesPriceFormulaModel], [TaxModel, SalesPriceFormulaModel, TransactionSettingModel],
CONNECTION_NAME.DEFAULT, CONNECTION_NAME.DEFAULT,
), ),
CqrsModule, CqrsModule,

View File

@ -31,6 +31,9 @@ export class PosTransactionHandler implements IEventHandler<ChangeDocEvent> {
) {} ) {}
async handle(event: ChangeDocEvent) { async handle(event: ChangeDocEvent) {
const envSkipTransaction = process.env.SKIP_TRANSACTION_FEATURE ?? 'false';
const activeSkipTransaction = envSkipTransaction == 'true';
const apmTransactions = apm.startTransaction( const apmTransactions = apm.startTransaction(
`ChangeDocEvent ${event?.data?.database}`, `ChangeDocEvent ${event?.data?.database}`,
'handler', 'handler',
@ -104,7 +107,17 @@ export class PosTransactionHandler implements IEventHandler<ChangeDocEvent> {
apmTransactions.setLabel('Code', data?.code); apmTransactions.setLabel('Code', data?.code);
apmTransactions.setLabel('Status', data?.status); apmTransactions.setLabel('Status', data?.status);
await this.dataService.create(queryRunner, TransactionModel, data); // Check if this transaction should be sent to the "black hole" (not saved)
// This is only applicable for SETTLED transactions
const shouldSkipSaving =
activeSkipTransaction &&
data.status === STATUS.SETTLED &&
(await this.formulaService.sentToBlackHole());
// If we shouldn't skip saving, then save the transaction
if (!shouldSkipSaving) {
await this.dataService.create(queryRunner, TransactionModel, data);
}
/** /**
* When transaction is cancel, set booking to Pending * When transaction is cancel, set booking to Pending

View File

@ -32,7 +32,10 @@ import { BatchConfirmDataTransactionManager } from './domain/usecases/managers/b
import { PosTransactionHandler } from './domain/usecases/handlers/pos-transaction.handler'; import { PosTransactionHandler } from './domain/usecases/handlers/pos-transaction.handler';
import { TaxDataService } from '../tax/data/services/tax-data.service'; import { TaxDataService } from '../tax/data/services/tax-data.service';
import { SalesPriceFormulaDataService } from '../sales-price-formula/data/services/sales-price-formula-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 {
SalesPriceFormulaModel,
TransactionSettingModel,
} from '../sales-price-formula/data/models/sales-price-formula.model';
import { TaxModel } from '../tax/data/models/tax.model'; import { TaxModel } from '../tax/data/models/tax.model';
import { SettledTransactionHandler } from './domain/usecases/handlers/settled-transaction.handler'; import { SettledTransactionHandler } from './domain/usecases/handlers/settled-transaction.handler';
import { RefundUpdatedHandler } from './domain/usecases/handlers/refund-update.handler'; import { RefundUpdatedHandler } from './domain/usecases/handlers/refund-update.handler';
@ -43,6 +46,7 @@ import { PaymentMethodModel } from '../payment-method/data/models/payment-method
import { TransactionDemographyModel } from './data/models/transaction-demography.model'; import { TransactionDemographyModel } from './data/models/transaction-demography.model';
import { PriceCalculator } from './domain/usecases/calculator/price.calculator'; import { PriceCalculator } from './domain/usecases/calculator/price.calculator';
import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'; import { ItemModel } from 'src/modules/item-related/item/data/models/item.model';
import { CouchModule } from 'src/modules/configuration/couch/couch.module';
@Module({ @Module({
exports: [TransactionReadService], exports: [TransactionReadService],
@ -57,6 +61,7 @@ import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'
TransactionTaxModel, TransactionTaxModel,
TransactionItemTaxModel, TransactionItemTaxModel,
TransactionBreakdownTaxModel, TransactionBreakdownTaxModel,
TransactionSettingModel,
TaxModel, TaxModel,
SalesPriceFormulaModel, SalesPriceFormulaModel,
PaymentMethodModel, PaymentMethodModel,
@ -65,6 +70,7 @@ import { ItemModel } from 'src/modules/item-related/item/data/models/item.model'
CONNECTION_NAME.DEFAULT, CONNECTION_NAME.DEFAULT,
), ),
CqrsModule, CqrsModule,
CouchModule,
], ],
controllers: [TransactionDataController, TransactionReadController], controllers: [TransactionDataController, TransactionReadController],
providers: [ providers: [

View File

@ -12,6 +12,7 @@ import {
} from './whatsapp.constant'; } from './whatsapp.constant';
import axios from 'axios'; import axios from 'axios';
import { Logger } from '@nestjs/common'; import { Logger } from '@nestjs/common';
import { apm } from 'src/core/apm';
export class WhatsappService { export class WhatsappService {
async sendMessage(data) { async sendMessage(data) {
@ -25,8 +26,14 @@ export class WhatsappService {
data: data, data: data,
}; };
const response = await axios(config); try {
return response.data; const response = await axios(config);
return response.data;
} catch (error) {
Logger.error(error);
apm?.captureError(error);
return null;
}
} }
async queueRegister(data: WhatsappQueue) { async queueRegister(data: WhatsappQueue) {
@ -91,8 +98,11 @@ export class WhatsappService {
}, },
}; };
await this.sendMessage(payload); const response = await this.sendMessage(payload);
Logger.log(`Notification register for ${data.code} send to ${data.phone}`); if (response)
Logger.log(
`Notification register for ${data.code} send to ${data.phone}`,
);
} }
async queueProcess(data: WhatsappQueue) { async queueProcess(data: WhatsappQueue) {
@ -152,7 +162,8 @@ export class WhatsappService {
}, },
}; };
await this.sendMessage(payload); const response = await this.sendMessage(payload);
Logger.log(`Notification process for ${data.code} send to ${data.phone}`); if (response)
Logger.log(`Notification process for ${data.code} send to ${data.phone}`);
} }
} }

578
yarn.lock

File diff suppressed because it is too large Load Diff