Merge pull request 'fix/data' (#40) from fix/data into development
continuous-integration/drone/tag Build is passing Details

Reviewed-on: #40
pull/41/head devel_10.6.28
aswin 2024-07-26 10:20:47 +00:00
commit c7fa402663
20 changed files with 214 additions and 38 deletions

View File

@ -97,7 +97,7 @@ export abstract class BaseCreateManager<Entity> extends BaseManager {
this.eventBus.publishAll([ this.eventBus.publishAll([
new topic.topic({ new topic.topic({
id: data?.['id'] ?? topic?.data?.['id'], id: this.result['id'],
old: null, old: null,
data: data ?? topic.data, data: data ?? topic.data,
user: this.user, user: this.user,

View File

@ -20,6 +20,16 @@ export enum ORDER_TYPE {
DESC = 'DESC', DESC = 'DESC',
} }
export const DAY = [
'minggu',
'senin',
'selasa',
'rabu',
'kamis',
'jumat',
'sabtu',
];
export enum CONNECTION_NAME { export enum CONNECTION_NAME {
DEFAULT = 'default', DEFAULT = 'default',
} }

View File

@ -0,0 +1,43 @@
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
import { EMPTY_UUID } from 'src/core/strings/constants/base.constants';
import { ItemCreatedEvent } from 'src/modules/item-related/item/domain/entities/event/item-created.event';
import { SeasonPeriodDataService } from 'src/modules/season-related/season-period/data/services/season-period-data.service';
import { Not } from 'typeorm';
import { ItemRateModel } from '../../../data/models/item-rate.model';
import { ItemRateDataService } from '../../../data/services/item-rate-data.service';
@EventsHandler(ItemCreatedEvent)
export class SeasonPeriodHolidayHandler
implements IEventHandler<ItemCreatedEvent>
{
constructor(
private seasonService: SeasonPeriodDataService,
private dataService: ItemRateDataService,
) {}
async handle(event: ItemCreatedEvent) {
const rates = [];
const seasons = await this.seasonService.getManyByOptions({
where: {
id: Not(EMPTY_UUID),
},
});
const queryRunner = this.dataService
.getRepository()
.manager.connection.createQueryRunner();
if (seasons.length) {
for (const season of seasons) {
const rate = new ItemRateModel();
rate.item_id = event.data.id;
rate.season_period_id = season.id;
rates.push(rate);
}
}
// create batch
await this.dataService.createBatch(queryRunner, ItemRateModel, rates);
}
}

View File

@ -6,7 +6,7 @@ import {
RelationParam, RelationParam,
} from 'src/core/modules/domain/entities/base-filter.entity'; } from 'src/core/modules/domain/entities/base-filter.entity';
import { ItemEntity } from 'src/modules/item-related/item/domain/entities/item.entity'; import { ItemEntity } from 'src/modules/item-related/item/domain/entities/item.entity';
import { STATUS } from 'src/core/strings/constants/base.constants'; import { DAY, STATUS } from 'src/core/strings/constants/base.constants';
@Injectable() @Injectable()
export class IndexItemRateManager extends BaseIndexManager<ItemEntity> { export class IndexItemRateManager extends BaseIndexManager<ItemEntity> {
@ -26,18 +26,32 @@ export class IndexItemRateManager extends BaseIndexManager<ItemEntity> {
d <= new Date(this.filterParam.end_date); d <= new Date(this.filterParam.end_date);
d.setDate(d.getDate() + 1) d.setDate(d.getDate() + 1)
) { ) {
const rate = item['item_rates']?.find( const day: string = DAY[d.getDay()];
(rate) => const rates = item['item_rates']
rate.season_period?.status == STATUS.ACTIVE && ?.filter((rate) => {
d >= new Date(rate.season_period.start_date) && const days: string[] = rate.season_period.days ?? [];
d <= new Date(rate.season_period.end_date), if (rate.season_period.priority == 2) {
); return (
rate.season_period?.status == STATUS.ACTIVE &&
d >= new Date(rate.season_period.start_date) &&
d <= new Date(rate.season_period.end_date) &&
days.includes(day)
);
} else {
return (
rate.season_period?.status == STATUS.ACTIVE &&
d >= new Date(rate.season_period.start_date) &&
d <= new Date(rate.season_period.end_date)
);
}
})
.sort((a, b) => a.season_period.priority - b.season_period.priority);
prices.push({ prices.push({
date: new Date(d), date: new Date(d),
price: rate?.price ?? item.base_price, price: rates[0]?.price ?? item.base_price,
season_type: rate?.season_period?.season_type ?? null, season_type: rates[0]?.season_period?.season_type ?? null,
holiday_name: rate?.season_period?.holiday_name ?? null, holiday_name: rates[0]?.season_period?.holiday_name ?? null,
}); });
} }
@ -91,6 +105,8 @@ export class IndexItemRateManager extends BaseIndexManager<ItemEntity> {
'season_period.holiday_name', 'season_period.holiday_name',
'season_period.start_date', 'season_period.start_date',
'season_period.end_date', 'season_period.end_date',
'season_period.priority',
'season_period.days',
'season_type.id', 'season_type.id',
'season_type.name', 'season_type.name',

View File

@ -16,16 +16,24 @@ import { UpdateItemRateManager } from './domain/usecases/managers/update-item-ra
import { DetailItemRateManager } from './domain/usecases/managers/detail-item-rate.manager'; import { DetailItemRateManager } from './domain/usecases/managers/detail-item-rate.manager';
import { BatchDeleteItemRateManager } from './domain/usecases/managers/batch-delete-item-rate.manager'; import { BatchDeleteItemRateManager } from './domain/usecases/managers/batch-delete-item-rate.manager';
import { ItemRateModel } from './data/models/item-rate.model'; import { ItemRateModel } from './data/models/item-rate.model';
import { SeasonPeriodHolidayHandler } from './domain/usecases/handlers/item-created.handler';
import { SeasonPeriodDataService } from 'src/modules/season-related/season-period/data/services/season-period-data.service';
import { SeasonPeriodModel } from 'src/modules/season-related/season-period/data/models/season-period.model';
@Global() @Global()
@Module({ @Module({
imports: [ imports: [
ConfigModule.forRoot(), ConfigModule.forRoot(),
TypeOrmModule.forFeature([ItemRateModel], CONNECTION_NAME.DEFAULT), TypeOrmModule.forFeature(
[ItemRateModel, SeasonPeriodModel],
CONNECTION_NAME.DEFAULT,
),
CqrsModule, CqrsModule,
], ],
controllers: [ItemRateDataController, ItemRateReadController], controllers: [ItemRateDataController, ItemRateReadController],
providers: [ providers: [
SeasonPeriodHolidayHandler,
IndexItemRateManager, IndexItemRateManager,
DetailItemRateManager, DetailItemRateManager,
CreateItemRateManager, CreateItemRateManager,
@ -33,6 +41,7 @@ import { ItemRateModel } from './data/models/item-rate.model';
UpdateItemRateManager, UpdateItemRateManager,
BatchDeleteItemRateManager, BatchDeleteItemRateManager,
SeasonPeriodDataService,
ItemRateDataService, ItemRateDataService,
ItemRateReadService, ItemRateReadService,

View File

@ -7,7 +7,11 @@ import {
import { ItemModel } from '../../../data/models/item.model'; import { ItemModel } from '../../../data/models/item.model';
import { ItemDeletedEvent } from '../../entities/event/item-deleted.event'; import { ItemDeletedEvent } from '../../entities/event/item-deleted.event';
import { BatchResult } from 'src/core/response/domain/ok-response.interface'; import { BatchResult } from 'src/core/response/domain/ok-response.interface';
import { Injectable } from '@nestjs/common'; import {
HttpStatus,
Injectable,
UnprocessableEntityException,
} from '@nestjs/common';
@Injectable() @Injectable()
export class BatchDeleteItemManager extends BaseBatchDeleteManager<ItemEntity> { export class BatchDeleteItemManager extends BaseBatchDeleteManager<ItemEntity> {
@ -16,6 +20,21 @@ export class BatchDeleteItemManager extends BaseBatchDeleteManager<ItemEntity> {
} }
async validateData(data: ItemEntity): Promise<void> { async validateData(data: ItemEntity): Promise<void> {
const haveRelation = await this.dataService.getOneByOptions({
where: {
bundling_items: {
id: data.id,
},
},
});
if (haveRelation) {
throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! this data already connected to bunding item`,
error: 'Unprocessable Entity',
});
}
return; return;
} }
@ -24,7 +43,7 @@ export class BatchDeleteItemManager extends BaseBatchDeleteManager<ItemEntity> {
} }
get validateRelations(): validateRelations[] { get validateRelations(): validateRelations[] {
return [{ relation: 'bundling_items' }]; return [];
} }
get entityTarget(): any { get entityTarget(): any {

View File

@ -1,4 +1,8 @@
import { Injectable } from '@nestjs/common'; import {
HttpStatus,
Injectable,
UnprocessableEntityException,
} from '@nestjs/common';
import { BaseDeleteManager } from 'src/core/modules/domain/usecase/managers/base-delete.manager'; import { BaseDeleteManager } from 'src/core/modules/domain/usecase/managers/base-delete.manager';
import { ItemEntity } from '../../entities/item.entity'; import { ItemEntity } from '../../entities/item.entity';
import { import {
@ -15,6 +19,21 @@ export class DeleteItemManager extends BaseDeleteManager<ItemEntity> {
} }
async validateProcess(): Promise<void> { async validateProcess(): Promise<void> {
const haveRelation = await this.dataService.getOneByOptions({
where: {
bundling_items: {
id: this.dataId,
},
},
});
if (haveRelation) {
throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! this data already connected to bunding item`,
error: 'Unprocessable Entity',
});
}
return; return;
} }
@ -27,7 +46,7 @@ export class DeleteItemManager extends BaseDeleteManager<ItemEntity> {
} }
get validateRelations(): validateRelations[] { get validateRelations(): validateRelations[] {
return [{ relation: 'bundling_items' }]; return [];
} }
get entityTarget(): any { get entityTarget(): any {

View File

@ -6,7 +6,7 @@ import {
Param, Param,
RelationParam, RelationParam,
} from 'src/core/modules/domain/entities/base-filter.entity'; } from 'src/core/modules/domain/entities/base-filter.entity';
import { STATUS } from 'src/core/strings/constants/base.constants'; import { DAY, STATUS } from 'src/core/strings/constants/base.constants';
@Injectable() @Injectable()
export class CurrentSeasonPeriodManager extends BaseIndexManager<SeasonPeriodEntity> { export class CurrentSeasonPeriodManager extends BaseIndexManager<SeasonPeriodEntity> {
@ -20,8 +20,19 @@ export class CurrentSeasonPeriodManager extends BaseIndexManager<SeasonPeriodEnt
} }
async afterProcess(): Promise<void> { async afterProcess(): Promise<void> {
const date = new Date(this.filterParam.date);
const day = DAY[date.getDay()];
Object.assign(this.result, { Object.assign(this.result, {
data: this.result.data.sort((a, b) => a.priority - b.priority), data: this.result.data
.filter((data) => {
const days: string[] = data.days ?? [];
if (data.priority == 2) {
return days.includes(day);
} else {
return true;
}
})
.sort((a, b) => a.priority - b.priority),
}); });
return; return;
} }

View File

@ -46,7 +46,7 @@ export class SeasonPeriodDataOrchestrator extends BaseDataTransactionOrchestrato
async update(dataId, data, updatePrice = false): Promise<SeasonPeriodEntity> { async update(dataId, data, updatePrice = false): Promise<SeasonPeriodEntity> {
this.updateManager.setData(dataId, { this.updateManager.setData(dataId, {
...data, ...data,
isUpdatePrice: updatePrice isUpdatePrice: updatePrice,
}); });
this.updateManager.setService(this.serviceData, TABLE_NAME.SEASON_PERIOD); this.updateManager.setService(this.serviceData, TABLE_NAME.SEASON_PERIOD);
await this.updateManager.execute(); await this.updateManager.execute();

View File

@ -23,7 +23,7 @@ export class BatchCancelReconciliationManager extends BaseBatchUpdateStatusManag
}, },
}); });
if (transaction.status != STATUS.SETTLED) { if ([STATUS.SETTLED, STATUS.WAITING].includes(transaction.status)) {
throw new UnprocessableEntityException({ throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY, statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! cant cancel transaction not settled`, message: `Failed! cant cancel transaction not settled`,

View File

@ -9,22 +9,20 @@ import { TransactionEntity } from 'src/modules/transaction/transaction/domain/en
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
import { STATUS } from 'src/core/strings/constants/base.constants'; import { STATUS } from 'src/core/strings/constants/base.constants';
import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event'; import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event';
import { generateInvoiceCodeHelper } from 'src/modules/transaction/transaction/domain/usecases/managers/helpers/generate-invoice-code.helper';
@Injectable() @Injectable()
export class BatchConfirmReconciliationManager extends BaseBatchUpdateStatusManager<TransactionEntity> { export class BatchConfirmReconciliationManager extends BaseBatchUpdateStatusManager<TransactionEntity> {
async validateData(data: TransactionEntity): Promise<void> { async validateData(data: TransactionEntity): Promise<void> {
const net_profit = data.reconciliation_mdr
? Number(this.data.payment_total) - Number(this.data.reconciliation_mdr)
: null;
Object.assign(data, { Object.assign(data, {
reconciliation_mdr: this.data.reconciliation_mdr ?? null,
reconciliation_confirm_by: this.user.name, reconciliation_confirm_by: this.user.name,
reconciliation_confirm_date: new Date().getTime(), reconciliation_confirm_date: new Date().getTime(),
status: STATUS.SETTLED, status: STATUS.SETTLED,
reconciliation_status: this.dataStatus, reconciliation_status: this.dataStatus,
payment_total_net_profit: net_profit, payment_code_reference: await generateInvoiceCodeHelper(
payment_date: this.data.payment_date, this.dataService,
'PMY',
),
}); });
return; return;
} }

View File

@ -27,7 +27,7 @@ export class CancelReconciliationManager extends BaseUpdateStatusManager<Transac
}, },
}); });
if (transaction.status != STATUS.SETTLED) { if ([STATUS.SETTLED, STATUS.WAITING].includes(transaction.status)) {
throw new UnprocessableEntityException({ throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY, statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! cant cancel transaction not settled`, message: `Failed! cant cancel transaction not settled`,

View File

@ -8,6 +8,7 @@ import {
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model'; import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event'; import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event';
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
import { generateInvoiceCodeHelper } from 'src/modules/transaction/transaction/domain/usecases/managers/helpers/generate-invoice-code.helper';
@Injectable() @Injectable()
export class ConfirmReconciliationManager extends BaseUpdateStatusManager<TransactionEntity> { export class ConfirmReconciliationManager extends BaseUpdateStatusManager<TransactionEntity> {
@ -25,6 +26,10 @@ export class ConfirmReconciliationManager extends BaseUpdateStatusManager<Transa
reconciliation_confirm_date: new Date().getTime(), reconciliation_confirm_date: new Date().getTime(),
status: STATUS.SETTLED, status: STATUS.SETTLED,
reconciliation_status: this.dataStatus, reconciliation_status: this.dataStatus,
payment_code_reference: await generateInvoiceCodeHelper(
this.dataService,
'PMY',
),
}); });
return; return;

View File

@ -7,6 +7,7 @@ import {
} from 'src/core/modules/domain/entities/base-filter.entity'; } from 'src/core/modules/domain/entities/base-filter.entity';
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity'; import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
import { BetweenQueryHelper } from 'src/core/helpers/query/between-query.helper'; import { BetweenQueryHelper } from 'src/core/helpers/query/between-query.helper';
import { STATUS } from 'src/core/strings/constants/base.constants';
@Injectable() @Injectable()
export class IndexReconciliationManager extends BaseIndexManager<TransactionEntity> { export class IndexReconciliationManager extends BaseIndexManager<TransactionEntity> {
@ -145,7 +146,10 @@ export class IndexReconciliationManager extends BaseIndexManager<TransactionEnti
} }
queryBuilder.andWhere( queryBuilder.andWhere(
`${this.tableName}.reconciliation_status Is Not Null`, `${this.tableName}.reconciliation_status Not In (:...statuses)`,
{
statuses: [STATUS.DRAFT],
},
); );
return queryBuilder; return queryBuilder;
} }

View File

@ -18,12 +18,29 @@ import { TransactionPaymentType } from '../../../constants';
@Injectable() @Injectable()
export class BatchConfirmDataTransactionManager extends BaseBatchUpdateStatusManager<TransactionEntity> { export class BatchConfirmDataTransactionManager extends BaseBatchUpdateStatusManager<TransactionEntity> {
validateData(data: TransactionEntity): Promise<void> { validateData(data: TransactionEntity): Promise<void> {
if (
[
TransactionPaymentType.BANK_TRANSFER,
TransactionPaymentType.QRIS,
].includes(this.data.payment_type) &&
(!this.data.payment_date ||
!this.data.payment_total ||
!this.data.payment_type_method_number ||
!this.data.payment_type_method_name)
) {
throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Gagal! tolong lengkapi data terlebih dahulu`,
error: 'Unprocessable Entity',
});
}
if ( if (
![STATUS.PENDING, STATUS.REJECTED, STATUS.EXPIRED].includes(data.status) ![STATUS.PENDING, STATUS.REJECTED, STATUS.EXPIRED].includes(data.status)
) { ) {
throw new UnprocessableEntityException({ throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY, statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! only data booking with status ${STATUS.PENDING}, ${STATUS.REJECTED}, ${STATUS.EXPIRED} can be confirm`, message: `Gagal! hanya pemesanan dengan status ${STATUS.PENDING}, ${STATUS.REJECTED}, ${STATUS.EXPIRED} dapat dikonfirmasi`,
error: 'Unprocessable Entity', error: 'Unprocessable Entity',
}); });
} }

View File

@ -49,7 +49,7 @@ export class BatchConfirmTransactionManager extends BaseBatchUpdateStatusManager
} }
Object.assign(data, { Object.assign(data, {
invoice_code: await generateInvoiceCodeHelper(this.dataService), invoice_code: await generateInvoiceCodeHelper(this.dataService, 'INV'),
status: freeTransaction ? STATUS.ACTIVE : STATUS.PENDING, status: freeTransaction ? STATUS.ACTIVE : STATUS.PENDING,
}); });
return; return;

View File

@ -21,6 +21,23 @@ export class ConfirmDataTransactionManager extends BaseUpdateStatusManager<Trans
} }
async validateProcess(): Promise<void> { async validateProcess(): Promise<void> {
if (
[
TransactionPaymentType.BANK_TRANSFER,
TransactionPaymentType.QRIS,
].includes(this.data.payment_type) &&
(!this.data.payment_date ||
!this.data.payment_total ||
!this.data.payment_type_method_number ||
!this.data.payment_type_method_name)
) {
throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Gagal! tolong lengkapi data terlebih dahulu`,
error: 'Unprocessable Entity',
});
}
return; return;
} }
@ -32,7 +49,7 @@ export class ConfirmDataTransactionManager extends BaseUpdateStatusManager<Trans
) { ) {
throw new UnprocessableEntityException({ throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY, statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Failed! only data booking with status ${STATUS.PENDING}, ${STATUS.REJECTED}, ${STATUS.EXPIRED} can be confirm`, message: `Gagal! hanya pemesanan dengan status ${STATUS.PENDING}, ${STATUS.REJECTED}, ${STATUS.EXPIRED} dapat dikonfirmasi`,
error: 'Unprocessable Entity', error: 'Unprocessable Entity',
}); });
} }

View File

@ -59,7 +59,7 @@ export class ConfirmTransactionManager extends BaseUpdateStatusManager<Transacti
invoice_code: invoice_code:
this.data.payment_type == TransactionPaymentType.COUNTER this.data.payment_type == TransactionPaymentType.COUNTER
? null ? null
: await generateInvoiceCodeHelper(this.dataService), : await generateInvoiceCodeHelper(this.dataService, 'INV'),
status: freeTransaction ? STATUS.ACTIVE : STATUS.PENDING, status: freeTransaction ? STATUS.ACTIVE : STATUS.PENDING,
}); });
return; return;

View File

@ -4,19 +4,26 @@ import {
} from 'src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper'; } from 'src/modules/transaction/vip-code/domain/usecases/managers/helpers/generate-random.helper';
import { ILike } from 'typeorm'; import { ILike } from 'typeorm';
export async function generateInvoiceCodeHelper(dataService) { export async function generateInvoiceCodeHelper(dataService, code) {
const month_year = generateCodeDate(); const month_year = generateCodeDate();
const char = generateRandom(1); const char = generateRandom(1);
const number = generateRandom(1, true); const number = generateRandom(1, true);
let query: any = {
invoice_code: ILike(`%${month_year}%`),
};
if (code == 'PYM') {
query = {
payment_code_reference: ILike(`%${month_year}%`),
};
}
const invoice_code = await dataService.getManyByOptions({ const invoice_code = await dataService.getManyByOptions({
where: { where: query,
invoice_code: ILike(`%${month_year}%`),
},
}); });
const current_number = invoice_code.length + 1; const current_number = invoice_code.length + 1;
return `INV-${month_year}${char}${number}/${current_number return `${code}-${month_year}${char}${number}/${current_number
.toString() .toString()
.padStart(5, '0')}`; .padStart(5, '0')}`;
} }

View File

@ -61,6 +61,7 @@ export function mappingTransaction(data) {
season_period: season_period, season_period: season_period,
items: items, items: items,
payment_type_bank: payment_type_bank, payment_type_bank: payment_type_bank,
payment_code: data.payment_code_reference,
}); });
delete data.season_period_id; delete data.season_period_id;