fix/data #38

Merged
aswin merged 2 commits from fix/data into development 2024-07-26 03:07:02 +00:00
18 changed files with 280 additions and 62 deletions

15
env/env.development vendored
View File

@ -22,15 +22,18 @@ CRON_MIDNIGHT="55 11 * * *"
CRON_EVERY_MINUTE="55 11 * * *"
CRON_EVERY_HOUR="0 * * * *"
EMAIL_HOST="sandbox.smtp.mailtrap.io"
EMAIL_HOST=smtp.gmail.com
EMAIL_POST=465
EMAIL_USER="developer@eigen.co.id"
EMAIL_TOKEN="bitqkbkzjzfywxqx"
EMAIL_USER=developer@eigen.co.id
EMAIL_TOKEN=bitqkbkzjzfywxqx
MIDTRANS_URL=https://app.sandbox.midtrans.com
MIDTRANS_PRODUCTION=false
MIDTRANS_SERVER_KEY=
MIDTRANS_CLIENT_KEY=
MIDTRANS_SERVER_KEY=SB-Mid-server-kH9_RBZrTwaUkxSrC5vOVaeG
MIDTRANS_CLIENT_KEY=SB-Mid-client-7XLwqG5cgjUmZj-7
EXPORT_LIMIT_PARTITION=200
ASSETS="https://asset.sky.eigen.co.id/"
ASSETS="https://asset.sky.eigen.co.id/"
GOOGLE_CALENDAR_KEY="AIzaSyCSg4P3uC9Z7kD1P4f3rf1BbBaz4Q-M55o"
GOOGLE_CALENDAR_ID="326464ac296874c7121825f5ef2e2799baa90b51da240f0045aae22beec10bd5@group.calendar.google.com"

15
env/env.production vendored
View File

@ -22,15 +22,18 @@ CRON_MIDNIGHT="55 11 * * *"
CRON_EVERY_MINUTE="55 11 * * *"
CRON_EVERY_HOUR="0 * * * *"
EMAIL_HOST="sandbox.smtp.mailtrap.io"
EMAIL_HOST=smtp.gmail.com
EMAIL_POST=465
EMAIL_USER=
EMAIL_TOKEN=
EMAIL_USER=weplayground.app@gmail.com
EMAIL_TOKEN=sonvvwiukhsevtmv
MIDTRANS_URL=https://app.midtrans.com
MIDTRANS_PRODUCTION=true
MIDTRANS_SERVER_KEY=
MIDTRANS_CLIENT_KEY=
MIDTRANS_SERVER_KEY=Mid-server-BZlPCcrWHDuSxW48oxBs5uAl
MIDTRANS_CLIENT_KEY=Mid-client-YhOPuo0NZPNZfiKq
EXPORT_LIMIT_PARTITION=200
ASSETS="https://asset.sky.eigen.co.id/"
ASSETS="https://asset.sky.eigen.co.id/"
GOOGLE_CALENDAR_KEY="AIzaSyCSg4P3uC9Z7kD1P4f3rf1BbBaz4Q-M55o"
GOOGLE_CALENDAR_ID="326464ac296874c7121825f5ef2e2799baa90b51da240f0045aae22beec10bd5@group.calendar.google.com"

13
google-credential.json Normal file
View File

@ -0,0 +1,13 @@
{
"type": "service_account",
"project_id": "weplayground-app",
"private_key_id": "e3ed1a4430140ac589c6e9e7ce125d16d8f7304a",
"private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDCmEl90K7ojdx1\nnJv5BKq3THI+l+pgC4dlqOuEV4sc2SFXECgyEgEYAFH6U8eH9TTl5wW5hFhvpiWl\nHSxZA2nMa1ojp97mkufzaGgsJbcB4ni9ydoJyN9Hqs2Wz+JiBtjscGOrmOP1bNyn\nBO9RhHInh0bfTNMrtsIicr4DPNIfM2sl95v6pCDGt7Cfu2NoEnDhId50d73KVONI\n0+rf90WhehEMwoZEzYI0gLmSVbnPEm1j4/OOQfQl7FjaFKyle+A5BWaiRsIqiSue\n0jvZz0DlGmjeHx1yjBIKpq5omOku7aYi4kTNEZKKxzs5HhRFKi2KuYNK/WD5ApQg\ncIhGhhCfAgMBAAECggEANX+LmNjh9VJm/Tigkt4LFxifwgCe8WfKAhNmKHyu5K/3\nIAnzmwxjG5ee8gzNat3pfJk+dCnj7FIHwHScSB6NnCMZZXsV51sVBNC77wMxZIXA\nPyE63fzJEdlt6xvc96k9QweFB1yhs0wJ/6r2JnmcrqxcujBTUA3PIoxcG+TBOc08\ndo5Rcbeq6/3txjGlFM1820WViuFSQQiL6PgNVb+l0JrQ8rAOflKYFOkUb8wux9LX\nnD4vJMwa0j+GRvH5BCcZCguIQZn2JR3rTgcavWtcaHiTNsc49Lsj/hGGOsbkFROo\nGWaSgXE169xiVR/MMEblzqpSXq1qXF2iUeaqyUFIZQKBgQDxxrNlDs1qMfcaQ0S2\nVVtU/f1NfY+kCjQaC4CoYJaaoZINs5ODPs8/2DGnHuhNXMtnPeQ+SzNaK1e1eLbw\nmvq1+n3aGZTvUq2L3b+v7JJ6TQmQ4eBLZBzNjxrxC3EkCULTuROtsAhfzORuE0mE\nwnhR5LpPraEBrPi0re9yDDXVHQKBgQDOCwGw1gNVLh622qR65Zhx5rs2q6ktPxq2\neiUV0KDug6/7QbJzg1pNeoVQmadJR86H0fzKMsN5C7t7z3MIkqXc0+T1NmdN2fPm\ndLthnR1grCDYykoet/CITbAfiip27/o3TJ7YIYItefyZ4GnNH82R/4z3LBDnXB9f\n565hbUj76wKBgEnNMpOFijSBXgFZSU8zDPcLtNeDnWYgazkMC9DZ8v7ulOuzxjKI\n6LB/aOCvsY9z5O712IcfY2SB2HsfhxA47pDADsyVhH3tSeZo4QttdmT4wRPFrza0\nL4qbxUiRCo9KeGiylQwusM+1doEXSBjLV/j/jdOml4AwcZaNhYrVqVUNAoGAU0uD\nzXdXNZJFfGp7X+t9a155hKp05APEyswqPd1vkbzO4eY3PBd35CaJyoGzbR6IUcQE\nS8Gl4ENr8at1t5uBTfqjbrYloQVhYmMCdX3MqI4tYTa2LCD0LkYp0zZJ4Hc3Ui+5\nb2psc/ICujpMy032DvWeiTXZR46oaF8C0gQaIy0CgYEAmKCP4CXmPlWoWqebFp3W\nz2eKWUfASioQ+ZGUVNEge4a6iutciydQJZxBfg9ZXWqDfI0FoRSPfs2zUZFO0AcM\n6oaPGiFnTnH8FGcSHu3p0YysevyoSY6tgsAhb3IiKjJd4e7btsYzpPZbIfyfUVHK\nQFOOSkE+x4J5ts+XO6isQ+w=\n-----END PRIVATE KEY-----\n",
"client_email": "weplayground@weplayground-app.iam.gserviceaccount.com",
"client_id": "106351339097550564510",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/weplayground%40weplayground-app.iam.gserviceaccount.com",
"universe_domain": "googleapis.com"
}

View File

@ -102,7 +102,7 @@ export abstract class BaseUpdateManager<Entity> extends BaseManager {
this.eventBus.publishAll([
new topic.topic({
id: data?.['id'] ?? topic?.data?.['id'],
id: topic.data?.['id'] ?? this.dataId,
old: this.oldData,
data: data ?? topic.data,
user: this.user,

View File

@ -0,0 +1,25 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddCalendarColumnTransaction1721892389807
implements MigrationInterface
{
name = 'AddCalendarColumnTransaction1721892389807';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transactions" ADD "calendar_id" character varying`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "calendar_link" character varying`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "calendar_id"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "calendar_link"`,
);
}
}

View File

@ -0,0 +1,78 @@
import { google } from 'googleapis';
import * as fs from 'fs';
import * as path from 'path';
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
export async function CreateEventCalendarHelper(
transaction: TransactionEntity,
isDelete = false,
) {
let result;
const filePath = path.join(
__dirname,
'../../../../../../../../',
'google-credential.json',
);
const credential = JSON.parse(fs.readFileSync(filePath, 'utf8'));
const client = new google.auth.JWT({
email: credential.client_email,
key: credential.private_key,
scopes: ['https://www.googleapis.com/auth/calendar'],
});
const calendar = google.calendar({
version: 'v3',
auth: client,
});
const eventData = mappingData(transaction);
if (transaction.calendar_id) {
result = await calendar.events.update(
{
calendarId: process.env.GOOGLE_CALENDAR_ID,
eventId: transaction.calendar_id,
requestBody: eventData,
},
{},
);
} else if (!isDelete) {
result = await calendar.events.insert(
{
calendarId: process.env.GOOGLE_CALENDAR_ID,
requestBody: eventData,
},
{},
);
} else {
result = await calendar.events.delete(
{
calendarId: process.env.GOOGLE_CALENDAR_ID,
eventId: transaction.calendar_id,
},
{},
);
}
return result?.data;
}
function mappingData(transaction) {
return {
summary: transaction.invoice_code,
description: `Booking for invoice ${transaction.invoice_code}`,
start: {
dateTime: new Date(transaction.booking_date).toISOString(),
timeZone: 'Asia/Jakarta',
},
end: {
dateTime: new Date(transaction.booking_date).toISOString(),
timeZone: 'Asia/Jakarta',
},
reminders: {
useDefault: false,
},
};
}

View File

@ -8,7 +8,7 @@ export class IndexHolidayCalendarManager {
const events = [];
const calendar = google.calendar({
version: 'v3',
auth: 'AIzaSyCsCt6PDd6uYLkahvtdvCoMWf-1_QaLiNM',
auth: process.env.GOOGLE_CALENDAR_KEY,
});
const calendarId = 'id.indonesian#holiday@group.v.calendar.google.com';

View File

@ -4,10 +4,29 @@ import { CqrsModule } from '@nestjs/cqrs';
import { IndexHolidayCalendarManager } from '../../configuration/google-calendar/domain/usecases/managers/index-holiday-google-calendar.manager';
import { GoogleCalendarController } from './infrastructure/google-calendar.controller';
import { GoogleCalendarOrchestrator } from './domain/usecases/google-calendar.orchestrator';
import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service';
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import { TransactionItemModel } from 'src/modules/transaction/transaction/data/models/transaction-item.model';
import { TransactionTaxModel } from 'src/modules/transaction/transaction/data/models/transaction-tax.model';
@Module({
imports: [ConfigModule.forRoot(), CqrsModule],
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forFeature(
[TransactionModel, TransactionItemModel, TransactionTaxModel],
CONNECTION_NAME.DEFAULT,
),
CqrsModule,
],
controllers: [GoogleCalendarController],
providers: [IndexHolidayCalendarManager, GoogleCalendarOrchestrator],
providers: [
IndexHolidayCalendarManager,
TransactionDataService,
GoogleCalendarOrchestrator,
],
})
export class GoogleCalendarModule {}

View File

@ -1,5 +1,5 @@
import { GoogleCalendarOrchestrator } from './../domain/usecases/google-calendar.orchestrator';
import { Controller, Get, Query } from '@nestjs/common';
import { Controller, Get, Post, Query } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Public } from 'src/core/guards';
import { FilterGoogleCalendarDto } from './dto/filter-google-calendar.dto';

View File

@ -12,6 +12,7 @@ import {
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
import { STATUS } from 'src/core/strings/constants/base.constants';
import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event';
@Injectable()
export class BatchCancelReconciliationManager extends BaseBatchUpdateStatusManager<TransactionEntity> {
@ -67,7 +68,11 @@ export class BatchCancelReconciliationManager extends BaseBatchUpdateStatusManag
}
get eventTopics(): EventTopics[] {
return [];
return [
{
topic: TransactionChangeStatusEvent,
},
];
}
getResult(): BatchResult {

View File

@ -8,6 +8,7 @@ import { Injectable } from '@nestjs/common';
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
import { STATUS } from 'src/core/strings/constants/base.constants';
import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event';
@Injectable()
export class BatchConfirmReconciliationManager extends BaseBatchUpdateStatusManager<TransactionEntity> {
@ -45,7 +46,11 @@ export class BatchConfirmReconciliationManager extends BaseBatchUpdateStatusMana
}
get eventTopics(): EventTopics[] {
return [];
return [
{
topic: TransactionChangeStatusEvent,
},
];
}
getResult(): BatchResult {

View File

@ -10,6 +10,7 @@ import {
validateRelations,
} from 'src/core/strings/constants/interface.constants';
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 { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
@Injectable()
@ -68,6 +69,10 @@ export class CancelReconciliationManager extends BaseUpdateStatusManager<Transac
}
get eventTopics(): EventTopics[] {
return [];
return [
{
topic: TransactionChangeStatusEvent,
},
];
}
}

View File

@ -6,6 +6,7 @@ import {
validateRelations,
} from 'src/core/strings/constants/interface.constants';
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 { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
@Injectable()
@ -42,6 +43,10 @@ export class ConfirmReconciliationManager extends BaseUpdateStatusManager<Transa
}
get eventTopics(): EventTopics[] {
return [];
return [
{
topic: TransactionChangeStatusEvent,
},
];
}
}

View File

@ -6,6 +6,7 @@ import {
validateRelations,
} from 'src/core/strings/constants/interface.constants';
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
import { TransactionUpdatedEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-updated.event';
import { TransactionEntity } from 'src/modules/transaction/transaction/domain/entities/transaction.entity';
@Injectable()
@ -46,6 +47,10 @@ export class UpdateReconciliationManager extends BaseUpdateManager<TransactionEn
}
get eventTopics(): EventTopics[] {
return [];
return [
{
topic: TransactionUpdatedEvent,
},
];
}
}

View File

@ -206,6 +206,13 @@ export class TransactionModel
@Column({ name: 'sending_qr_at', type: 'bigint', nullable: true })
sending_qr_at: number;
// calendar
@Column('varchar', { name: 'calendar_id', nullable: true })
calendar_id: string;
@Column('varchar', { name: 'calendar_link', nullable: true })
calendar_link: string;
// relations to item
@OneToMany(() => TransactionItemModel, (model) => model.transaction, {
cascade: true,

View File

@ -78,4 +78,7 @@ export interface TransactionEntity extends BaseStatusEntity {
sending_invoice_status: STATUS;
sending_qr_at: number;
sending_qr_status: STATUS;
calendar_id?: string;
calendar_link?: string;
}

View File

@ -1,23 +1,36 @@
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
import { EventBus, EventsHandler, IEventHandler } from '@nestjs/cqrs';
import { MidtransCallbackEvent } from 'src/modules/configuration/midtrans/domain/entities/midtrans-callback.event';
import { TransactionDataService } from '../../../data/services/transaction-data.service';
import { TransactionModel } from '../../../data/models/transaction.model';
import { STATUS } from 'src/core/strings/constants/base.constants';
import {
BLANK_USER,
OPERATION,
STATUS,
} from 'src/core/strings/constants/base.constants';
import { TransactionChangeStatusEvent } from '../../entities/event/transaction-change-status.event';
import * as _ from 'lodash';
import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
@EventsHandler(MidtransCallbackEvent)
export class MidtransCallbackHandler
implements IEventHandler<MidtransCallbackEvent>
{
constructor(private dataService: TransactionDataService) {}
constructor(
private dataService: TransactionDataService,
private eventBus: EventBus,
) {}
async handle(event: MidtransCallbackEvent) {
const data_id = event.data.id;
const data = event.data.data;
let old_data;
const transaction = await this.dataService.getOneByOptions({
where: {
id: data_id,
},
});
old_data = _.cloneDeep(transaction);
if (['capture', 'settlement'].includes(data.transaction_status)) {
Object.assign(transaction, {
@ -40,5 +53,17 @@ export class MidtransCallbackHandler
.manager.connection.createQueryRunner();
await this.dataService.create(queryRunner, TransactionModel, transaction);
this.eventBus.publish(
new TransactionChangeStatusEvent({
id: data_id,
old: old_data,
data: data,
user: BLANK_USER,
description: 'Midtrans Callback',
module: TABLE_NAME.TRANSACTION,
op: OPERATION.UPDATE,
}),
);
}
}

View File

@ -7,8 +7,10 @@ import { STATUS } from 'src/core/strings/constants/base.constants';
import { TransactionDataService } from '../../../data/services/transaction-data.service';
import { TransactionModel } from '../../../data/models/transaction.model';
import { calculateSalesFormula } from 'src/modules/transaction/sales-price-formula/domain/usecases/managers/helpers/calculation-formula.helper';
import { CreateEventCalendarHelper } from 'src/modules/configuration/google-calendar/domain/usecases/managers/helpers/create-event-calanedar.helper';
import { TransactionUpdatedEvent } from '../../entities/event/transaction-updated.event';
@EventsHandler(TransactionChangeStatusEvent)
@EventsHandler(TransactionChangeStatusEvent, TransactionUpdatedEvent)
export class SettledTransactionHandler
implements IEventHandler<TransactionChangeStatusEvent>
{
@ -21,55 +23,70 @@ export class SettledTransactionHandler
async handle(event: TransactionChangeStatusEvent) {
const old_data = event.data.old;
const current_data = event.data.data;
if (
old_data.status == current_data.status ||
![STATUS.ACTIVE, STATUS.SETTLED].includes(current_data.status)
)
return;
const settled = [STATUS.ACTIVE, STATUS.SETTLED].includes(
current_data.status,
);
const oldSettled = [STATUS.ACTIVE, STATUS.SETTLED].includes(
old_data.status,
);
const data = await this.dataService.getOneByOptions({
where: {
id: current_data.id,
id: event.data.id,
},
relations: ['items'],
});
const profit_formula = await this.formulaService.getOneByOptions({
where: {
type: FormulaType.PROFIT_SHARE,
},
});
const sales_price = await this.formulaService.getOneByOptions({
where: {
type: FormulaType.SALES_PRICE,
},
});
const taxes = await this.taxService.getManyByOptions({
where: {
status: STATUS.ACTIVE,
},
});
if (!settled || !oldSettled) return;
const queryRunner = this.dataService
.getRepository()
.manager.connection.createQueryRunner();
// const profit_share_value = this.calculateFormula(profit_formula.formula_string, taxes, data.payment_total_net_profit ?? 0);
const { dpp_value, tax_datas } = calculateSalesFormula(
sales_price.formula_string,
taxes,
data.payment_total_net_profit ?? 0,
);
if (settled) {
const profit_formula = await this.formulaService.getOneByOptions({
where: {
type: FormulaType.PROFIT_SHARE,
},
});
Object.assign(data, {
payment_total_dpp: dpp_value,
profit_share_formula: profit_formula.formula_string,
sales_price_formula: sales_price.formula_string,
taxes: tax_datas,
});
const sales_price = await this.formulaService.getOneByOptions({
where: {
type: FormulaType.SALES_PRICE,
},
});
const taxes = await this.taxService.getManyByOptions({
where: {
status: STATUS.ACTIVE,
},
});
// const profit_share_value = this.calculateFormula(profit_formula.formula_string, taxes, data.payment_total_net_profit ?? 0);
const { dpp_value, tax_datas } = calculateSalesFormula(
sales_price.formula_string,
taxes,
data.payment_total_net_profit ?? 0,
);
const google_calendar = await CreateEventCalendarHelper(data);
Object.assign(data, {
payment_total_dpp: dpp_value,
profit_share_formula: profit_formula.formula_string,
sales_price_formula: sales_price.formula_string,
taxes: tax_datas,
calendar_id: google_calendar?.id,
calendar_link: google_calendar?.htmlLink,
});
} else if (oldSettled) {
const google_calendar = await CreateEventCalendarHelper(data);
Object.assign(data, {
calendar_id: null,
calendar_link: null,
});
}
await this.dataService.create(queryRunner, TransactionModel, data);
}