Merge branch 'development' of ssh://git.eigen.co.id:2222/eigen/pos-be into staging

staging
shancheas 2024-07-22 19:03:40 +07:00
commit 46b92afb89
438 changed files with 17829 additions and 820 deletions

5
.gitignore vendored
View File

@ -35,4 +35,7 @@ docker-compose.yml
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/extensions.json
# IGNORE UPLOAD FOLDER
/uploads

19
env/env.development vendored
View File

@ -6,6 +6,8 @@ JWT_REFRESH_EXPIRES="7d"
ENC_KEY="921c83f3b90c92dca4ba9b947f99b4c9"
IV="a671a96159e97a4f"
COUCHDB_CONFIG="http://root:password@172.10.10.2:5970"
DEFAULT_DB_HOST="postgres"
DEFAULT_DB_PORT="5432"
DEFAULT_DB_USER="root"
@ -15,3 +17,20 @@ DEFAULT_DB_NAME="pos"
ELASTIC_APM_ACTIVATE=true
ELASTIC_APM_SERVICE_NAME="Skyworld POS"
ELASTIC_APM_SERVER_URL="http://172.10.10.10:8200"
CRON_MIDNIGHT="55 11 * * *"
CRON_EVERY_MINUTE="55 11 * * *"
CRON_EVERY_HOUR="0 * * * *"
EMAIL_HOST="sandbox.smtp.mailtrap.io"
EMAIL_POST=465
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=
EXPORT_LIMIT_PARTITION=200
ASSETS="https://asset.sky.eigen.co.id/"

36
env/env.production vendored Normal file
View File

@ -0,0 +1,36 @@
PORT="3346"
JWT_SECRET="ftyYM4t4kjuj/0ixvIrS18gpdvBJw42NnW71GrFrEhcn0alQkkH7TQIHU5MFFJ1e"
JWT_EXPIRES="24h"
JWT_REFRESH_EXPIRES="7d"
ENC_KEY="921c83f3b90c92dca4ba9b947f99b4c9"
IV="a671a96159e97a4f"
COUCHDB_CONFIG="http://root:password@172.10.10.2:5970"
DEFAULT_DB_HOST="postgres"
DEFAULT_DB_PORT="5432"
DEFAULT_DB_USER="root"
DEFAULT_DB_PASS="password"
DEFAULT_DB_NAME="pos"
ELASTIC_APM_ACTIVATE=true
ELASTIC_APM_SERVICE_NAME="Skyworld POS"
ELASTIC_APM_SERVER_URL="http://172.10.10.10:8200"
CRON_MIDNIGHT="55 11 * * *"
CRON_EVERY_MINUTE="55 11 * * *"
CRON_EVERY_HOUR="0 * * * *"
EMAIL_HOST="sandbox.smtp.mailtrap.io"
EMAIL_POST=465
EMAIL_USER=
EMAIL_TOKEN=
MIDTRANS_URL=https://app.midtrans.com
MIDTRANS_PRODUCTION=true
MIDTRANS_SERVER_KEY=
MIDTRANS_CLIENT_KEY=
EXPORT_LIMIT_PARTITION=200
ASSETS="https://asset.sky.eigen.co.id/"

42
formula-readme.md Normal file
View File

@ -0,0 +1,42 @@
## Formula Calculation
### Instalation
```
yarn add mathjs algebra.js
```
### Example
```ts
import * as math from 'mathjs'
import { Equation, parse } from 'algebra.js'
const formula = 'dpp - (dpp*ppn) - (dpp*retribusi) - (dpp*service) - (dpp*ppn3)'
const total = '300000'
const variable = {
ppn: 11,
retribusi: 5000,
service: 5,
ppn3: 5000
}
try {
const x1 = math.simplify(formula, variable).toString()
console.log('Formula ', x1)
const dppFormula = parse(x1)
const totalFormula = parse(total)
const equation = new Equation(totalFormula, dppFormula)
console.log(equation.toString())
const result = equation.solveFor('dpp').toString()
console.log(result)
const value = math.evaluate(result)
console.log(value)
} catch (e) {
console.log(e)
}
```

View File

@ -34,15 +34,25 @@
"@nestjs/cqrs": "^10.2.7",
"@nestjs/jwt": "^10.2.0",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/schedule": "^4.1.0",
"@nestjs/swagger": "^7.3.1",
"@nestjs/typeorm": "^10.0.2",
"@types/multer": "^1.4.11",
"algebra.js": "^0.2.6",
"bcrypt": "^5.1.1",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"dotenv": "^16.4.5",
"elastic-apm-node": "^4.5.4",
"exceljs": "^4.4.0",
"fs-extra": "^11.2.0",
"googleapis": "^140.0.0",
"handlebars": "^4.7.8",
"mathjs": "^13.0.2",
"midtrans-client": "^1.3.1",
"moment": "^2.30.1",
"nano": "^10.1.3",
"nodemailer": "^6.9.14",
"pg": "^8.11.5",
"plop": "^4.0.1",
"reflect-metadata": "^0.2.0",
@ -90,4 +100,4 @@
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}
}

View File

@ -29,7 +29,7 @@ module.exports = function (plop) {
name: 'location',
message: 'Location: ',
choices: function () {
return ['item related', 'user related', 'season related', 'transaction'];
return ['item related', 'user related', 'season related', 'transaction', 'web information'];
},
},
],

View File

@ -42,6 +42,33 @@ import { SeasonPeriodModel } from './modules/season-related/season-period/data/m
import { ItemRateModule } from './modules/item-related/item-rate/item-rate.module';
import { ItemRateModel } from './modules/item-related/item-rate/data/models/item-rate.model';
import { GoogleCalendarModule } from './modules/configuration/google-calendar/google-calendar.module';
import { TransactionModule } from './modules/transaction/transaction/transaction.module';
import { TransactionModel } from './modules/transaction/transaction/data/models/transaction.model';
import { TransactionItemModel } from './modules/transaction/transaction/data/models/transaction-item.model';
import { TransactionTaxModel } from './modules/transaction/transaction/data/models/transaction-tax.model';
import { ReconciliationModule } from './modules/transaction/reconciliation/reconciliation.module';
import { ReportModule } from './modules/reports/report/report.module';
import { ReportBookmarkModule } from './modules/reports/report-bookmark/report-bookmark.module';
import { ReportExportModule } from './modules/reports/report-export/report-export.module';
import { ReportBookmarkModel } from './modules/reports/shared/models/report-bookmark.model';
import { ExportReportHistoryModel } from './modules/reports/shared/models/export-report-history.model';
import { CronModule } from './modules/configuration/cron/cron.module';
import { MidtransModule } from './modules/configuration/midtrans/midtrans.module';
import { RefundModule } from './modules/transaction/refund/refund.module';
import { RefundModel } from './modules/transaction/refund/data/models/refund.model';
import { RefundItemModel } from './modules/transaction/refund/data/models/refund-item.model';
import { GateModule } from './modules/web-information/gate/gate.module';
import { GateModel } from './modules/web-information/gate/data/models/gate.model';
import { TermConditionModule } from './modules/web-information/term-condition/term-condition.module';
import { TermConditionModel } from './modules/web-information/term-condition/data/models/term-condition.model';
import { FaqModel } from './modules/web-information/faq/data/models/faq.model';
import { FaqModule } from './modules/web-information/faq/faq.module';
import { UploadModule } from './modules/configuration/upload/upload.module';
import { NewsModule } from './modules/web-information/news/news.module';
import { NewsModel } from './modules/web-information/news/data/models/news.model';
import { BannerModule } from './modules/web-information/banner/banner.module';
import { BannerModel } from './modules/web-information/banner/data/models/banner.model';
import { MailModule } from './modules/configuration/mail/mail.module';
@Module({
imports: [
@ -59,19 +86,33 @@ import { GoogleCalendarModule } from './modules/configuration/google-calendar/go
database: process.env.DEFAULT_DB_NAME,
entities: [
...UserPrivilegeModels,
BannerModel,
ErrorLogModel,
FaqModel,
GateModel,
ItemModel,
ItemCategoryModel,
ItemRateModel,
LogModel,
NewsModel,
PaymentMethodModel,
RefundModel,
RefundItemModel,
SalesPriceFormulaModel,
SeasonPeriodModel,
SeasonTypeModel,
TaxModel,
TermConditionModel,
TransactionModel,
TransactionItemModel,
TransactionTaxModel,
UserModel,
VipCategoryModel,
VipCodeModel,
// report
ReportBookmarkModel,
ExportReportHistoryModel,
],
synchronize: false,
}),
@ -79,9 +120,13 @@ import { GoogleCalendarModule } from './modules/configuration/google-calendar/go
ConstantModule,
CqrsModule,
CouchModule,
CronModule,
GoogleCalendarModule,
LogModule,
MailModule,
MidtransModule,
SessionModule,
UploadModule,
// user
TenantModule,
@ -96,14 +141,29 @@ import { GoogleCalendarModule } from './modules/configuration/google-calendar/go
// transaction
PaymentMethodModule,
ProfitShareFormulaModule,
ReconciliationModule,
RefundModule,
SalesPriceFormulaModule,
TaxModule,
TransactionModule,
VipCategoryModule,
VipCodeModule,
// session
SeasonTypeModule,
SeasonPeriodModule,
// web information
BannerModule,
FaqModule,
GateModule,
NewsModule,
TermConditionModule,
// report
ReportModule,
ReportBookmarkModule,
ReportExportModule,
],
controllers: [],
providers: [

View File

@ -61,7 +61,7 @@ export class PrivilegeService {
}
async privilegeConfiguration(): Promise<UserPrivilegeConfigurationEntity> {
const { module, menu, sub_menu, section } = this.moduleKey();
const { module, menu } = this.moduleKey();
return await this.repository.findOne({
select: ['id', 'view', 'create', 'edit', 'delete', 'cancel', 'confirm'],
where: {

View File

@ -0,0 +1,28 @@
import * as fs from 'fs-extra';
import * as path from 'path';
export async function MoveFilePathHelper(data) {
const imagePath = data['qr_image'] ?? data['image_url'];
const sourcePath = path.join(__dirname, '../../../../uploads/', imagePath);
const movePath =
'data/' +
imagePath
.split('/')
.filter((item) => !['uploads', 'tmp'].includes(item))
.join('/');
const destinationPath = path.join(
__dirname,
'../../../../uploads/',
movePath,
);
try {
await fs.move(sourcePath, destinationPath);
Object.assign(data, {
image_url: movePath,
});
} catch (error) {
console.log(`Failed! Error move file data`);
}
}

View File

@ -0,0 +1,44 @@
import { extname } from 'path';
import { v4 as uuidv4 } from 'uuid';
import { HttpException, HttpStatus } from '@nestjs/common';
import * as fs from 'fs';
import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface';
import { diskStorage } from 'multer';
const MB = 1024 * 1024;
const fileFilter = (req, file, callback) => {
if (file.mimetype.match(/\/(jpg|jpeg|png)$/)) {
callback(null, true);
} else {
callback(
new HttpException(
`Unsupported file type ${extname(file.originalname)}`,
HttpStatus.BAD_REQUEST,
),
false,
);
}
};
const editFileName = (req, file, callback) => {
const fileExtName = extname(file.originalname);
const randomName = uuidv4();
callback(null, `${randomName}${fileExtName}`);
};
const destinationPath = (req, file, cb) => {
let modulePath = req.body.module;
if (req.body.sub_module) modulePath = `${modulePath}/${req.body.sub_module}`;
fs.mkdirSync(`./uploads/tmp/${modulePath}`, { recursive: true });
cb(null, `./uploads/tmp/${modulePath}`);
};
export const StoreFileConfig: MulterOptions = {
storage: diskStorage({
destination: destinationPath,
filename: editFileName,
}),
fileFilter: fileFilter,
};

View File

@ -10,39 +10,14 @@ export function setQueryFilterDefault(
baseFilter: BaseFilterEntity,
tableName: TABLE_NAME,
): SelectQueryBuilder<any> {
// filter berdasarkan statuses
if (!!baseFilter.statuses) {
queryBuilder.andWhere(
new Brackets((qb) => {
baseFilter.statuses.map((status) => {
// trim search
const statusData = status.includes("'")
? status.trim().replace(/'/g, "''").replace(/\s+/g, ' ')
: status.trim().replace(/\s+/g, ' ');
// jika searching status terdapat dalam enum, maka dia mencari specific data
// ? karena jika tidak, ketika dia search "active" maka "inactive" juga ikut
if (STATUS[statusData.toUpperCase()])
qb.orWhere(`${tableName}.status = :statusData`, {
statusData: statusData,
});
else
qb['orWhere'](
`${tableName}.status::text ILIKE '%${[statusData]}%'`,
);
});
}),
);
}
// filter berdasarkan id pembuat
if (!!baseFilter.created_ids)
new WhereInQueryHelper(
queryBuilder,
tableName,
'created_id',
'creator_id',
baseFilter.created_ids,
'created_ids',
'creator_ids',
).getQuery();
// filter berdasarkan tanggal terakhir dibuat

View File

@ -63,7 +63,7 @@ export class ValidateRelationHelper<Entity> {
)
)
throw new UnprocessableEntityException(message);
} else if (data[`total_${relation.relation} `])
} else if (data[`total_${relation.relation}`] > 0)
throw new UnprocessableEntityException(message);
}
}

View File

@ -21,6 +21,15 @@ export abstract class BaseDataService<Entity> {
return await queryRunner.manager.save(newEntity);
}
async createMany(
queryRunner: QueryRunner,
entityTarget: EntityTarget<Entity>,
entity: Entity[],
): Promise<Entity[]> {
const newEntity = queryRunner.manager.create(entityTarget, entity);
return await queryRunner.manager.save(newEntity);
}
async createBatch(
queryRunner: QueryRunner,
entityTarget: EntityTarget<Entity>,
@ -68,4 +77,8 @@ export abstract class BaseDataService<Entity> {
async getOneByOptions(findOneOptions): Promise<Entity> {
return await this.repository.findOne(findOneOptions);
}
async getManyByOptions(findOneOptions): Promise<Entity[]> {
return await this.repository.find(findOneOptions);
}
}

View File

@ -13,9 +13,11 @@ export abstract class BaseReadService<Entity> {
queryBuilder: SelectQueryBuilder<Entity>,
params: BaseFilterEntity,
): Promise<PaginationResponse<Entity>> {
const limit = params.limit ?? 10;
const page = params.page ?? 1;
const [data, total] = await queryBuilder
.take(+params.limit)
.skip(+params.limit * +params.page - +params.limit)
.take(+limit)
.skip(+limit * +page - +limit)
.getManyAndCount();
return {

View File

@ -25,7 +25,7 @@ export abstract class BaseBatchDeleteManager<Entity> extends BaseManager {
async process(): Promise<void> {
let totalFailed = 0;
let totalSuccess = 0;
let messages = [];
const messages = [];
for (const id of this.dataIds) {
try {

View File

@ -4,11 +4,14 @@ import { HttpStatus, NotFoundException } from '@nestjs/common';
import { OPERATION, STATUS } from 'src/core/strings/constants/base.constants';
import { ValidateRelationHelper } from 'src/core/helpers/validation/validate-relation.helper';
import { RecordLog } from 'src/modules/configuration/log/domain/entities/log.event';
import * as _ from 'lodash';
export abstract class BaseBatchUpdateStatusManager<Entity> extends BaseManager {
protected dataIds: string[];
protected relations: string[] = [];
protected result: BatchResult;
protected dataStatus: STATUS;
protected oldData: Entity;
abstract get entityTarget(): any;
setData(ids: string[], status: STATUS): void {
@ -27,7 +30,7 @@ export abstract class BaseBatchUpdateStatusManager<Entity> extends BaseManager {
async process(): Promise<void> {
let totalFailed = 0;
let totalSuccess = 0;
let messages = [];
const messages = [];
for (const id of this.dataIds) {
try {
@ -35,6 +38,7 @@ export abstract class BaseBatchUpdateStatusManager<Entity> extends BaseManager {
where: {
id: id,
},
relations: this.relations,
});
if (!entity) {
@ -44,6 +48,13 @@ export abstract class BaseBatchUpdateStatusManager<Entity> extends BaseManager {
error: 'Entity Not Found',
});
}
this.oldData = _.cloneDeep(entity);
Object.assign(entity, {
status: this.dataStatus,
editor_id: this.user.id,
editor_name: this.user.name,
updated_at: new Date().getTime(),
});
await this.validateData(entity);
await new ValidateRelationHelper(
@ -57,15 +68,10 @@ export abstract class BaseBatchUpdateStatusManager<Entity> extends BaseManager {
this.queryRunner,
this.entityTarget,
{ id: id },
{
status: this.dataStatus,
editor_id: this.user.id,
editor_name: this.user.name,
updated_at: new Date().getTime(),
},
entity,
);
this.publishEvents(entity, result);
this.publishEvents(this.oldData, result);
totalSuccess = totalSuccess + 1;
} catch (error) {
@ -101,7 +107,7 @@ export abstract class BaseBatchUpdateStatusManager<Entity> extends BaseManager {
if (!this.eventTopics.length) return;
for (const topic of this.eventTopics) {
let data;
if (!topic.relations) {
if (topic.relations?.length) {
data = await this.dataService.getOneByOptions({
where: {
id: dataNew.id,

View File

@ -0,0 +1,152 @@
import { BaseManager } from '../base.manager';
import {
EventTopics,
columnUniques,
validateRelations,
} from 'src/core/strings/constants/interface.constants';
import { HttpStatus, UnprocessableEntityException } from '@nestjs/common';
import { SelectQueryBuilder } from 'typeorm';
export abstract class BaseChangePosition<Entity> extends BaseManager {
protected result: Entity;
protected duplicateColumn: string[];
protected startData: Entity;
protected endData: Entity;
protected columnSort: string;
protected firstDataId: number;
protected lastSort: number;
protected sortTo: number;
abstract get entityTarget(): any;
setData(entity: Entity, columnSort: string): void {
this.data = entity;
this.columnSort = columnSort;
}
async beforeProcess(): Promise<void> {
if (!this.data?.end || this.data.start == this.data?.end) {
throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: 'Please drag to another position',
error: 'Unprocessable Entity',
});
}
this.startData = await this.dataService.getOneByOptions({
where: {
id: this.data.start,
},
});
if (!this.startData) {
throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Entity with id : ${this.data.start} not found`,
error: 'Unprocessable Entity',
});
}
this.endData = await this.dataService.getOneByOptions({
where: {
id: this.data.end,
},
});
if (!this.endData) {
throw new UnprocessableEntityException({
statusCode: HttpStatus.UNPROCESSABLE_ENTITY,
message: `Entity with id : ${this.data.end} not found`,
error: 'Unprocessable Entity',
});
}
if (this.endData[this.columnSort] > this.startData[this.columnSort]) {
// drag from up
this.firstDataId = this.startData[this.columnSort];
this.lastSort = this.endData[this.columnSort];
this.sortTo = this.lastSort;
} else if (
this.endData[this.columnSort] < this.startData[this.columnSort]
) {
// drag from bottom
this.firstDataId = this.endData[this.columnSort];
this.lastSort = this.startData[this.columnSort];
this.sortTo = this.firstDataId;
}
}
async prepareData(): Promise<void> {
Object.assign(this.data, {
creator_id: this.user.id,
creator_name: this.user.name,
created_at: new Date().getTime(),
updated_at: new Date().getTime(),
});
}
async validateProcess(): Promise<void> {
return;
}
async process(): Promise<void> {
let dataArrange: Entity[];
const queryBuilder = this.dataService
.getRepository()
.createQueryBuilder(this.tableName)
.where(`${this.tableName}.${this.columnSort} between :data1 and :data2`, {
data1: this.firstDataId,
data2: this.lastSort,
});
const datas = await queryBuilder
.orderBy(`${this.tableName}.${this.columnSort}`, 'ASC')
.getManyAndCount();
if (datas[0].length) {
let dataFirst = datas[0][0][this.columnSort];
const data = datas[0];
const length = datas[1];
if (this.endData[this.columnSort] > this.startData[this.columnSort]) {
// drag from above
const dataDragged = data[0];
const arraySlice = data.slice(1, length);
dataArrange = arraySlice.concat([dataDragged]);
} else if (
this.endData[this.columnSort] < this.startData[this.columnSort]
) {
// drag from bottom
const dataDragged = data[length - 1];
const arraySlice = data.slice(0, length - 1);
dataArrange = [dataDragged].concat(arraySlice);
}
for (let i = 0; i < length; i++) {
dataArrange[i][this.columnSort] = dataFirst;
dataFirst++;
}
await this.dataService.createMany(
this.queryRunner,
this.entityTarget,
dataArrange,
);
}
}
get validateRelations(): validateRelations[] {
return [];
}
get eventTopics(): EventTopics[] {
return [];
}
getResult(): string {
return `Success! Data ${this.startData['name']} successfully moved to ${this.sortTo}`;
}
}

View File

@ -6,6 +6,7 @@ import {
columnUniques,
validateRelations,
} from 'src/core/strings/constants/interface.constants';
import { MoveFilePathHelper } from 'src/core/helpers/path/move-file-path.helper';
export abstract class BaseCreateManager<Entity> extends BaseManager {
protected result: Entity;
@ -43,6 +44,15 @@ export abstract class BaseCreateManager<Entity> extends BaseManager {
}
async process(): Promise<void> {
const keys = Object.keys(this.data);
if (
(keys.includes('qr_image') || keys.includes('image_url')) &&
(this.data['image_url']?.includes('tmp') ||
this.data['qr_image']?.includes('tmp'))
) {
await MoveFilePathHelper(this.data);
}
this.result = await this.dataService.create(
this.queryRunner,
this.entityTarget,
@ -75,11 +85,21 @@ export abstract class BaseCreateManager<Entity> extends BaseManager {
if (!this.eventTopics.length) return;
for (const topic of this.eventTopics) {
let data;
if (!topic.data) {
data = await this.dataService.getOneByOptions({
where: {
id: this.result['id'],
},
relations: topic.relations,
});
}
this.eventBus.publishAll([
new topic.topic({
id: this.result['id'],
id: data?.['id'] ?? topic?.data?.['id'],
old: null,
data: topic.data,
data: data ?? topic.data,
user: this.user,
description: '',
module: this.tableName,

View File

@ -5,7 +5,7 @@ export abstract class BaseCustomManager<Entity> extends BaseManager {
protected result: any;
abstract get entityTarget(): any;
setData(entity: Entity): void {
setData(entity: any): void {
this.data = entity;
}

View File

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

View File

@ -8,6 +8,7 @@ import {
} from 'src/core/helpers/query/default-filter.helper';
import { Param } from '../../entities/base-filter.entity';
import { joinRelationHelper } from 'src/core/helpers/query/join-relations.helper';
import { STATUS } from 'src/core/strings/constants/base.constants';
export abstract class BaseIndexManager<Entity> extends BaseReadManager {
protected result: PaginationResponse<Entity>;
@ -19,6 +20,7 @@ export abstract class BaseIndexManager<Entity> extends BaseReadManager {
}
async process(): Promise<void> {
const specificFilter = this.specificFilter;
const { joinRelations, selectRelations, countRelations } = this.relations;
if (joinRelations?.length)
@ -40,10 +42,26 @@ export abstract class BaseIndexManager<Entity> extends BaseReadManager {
if (this.selects?.length) this.queryBuilder.select(this.selects);
if (this.filterParam.statuses?.length > 0) {
const data = this.filterParam.statuses.map((status) => {
const statusData = status.includes("'")
? status.trim().replace(/'/g, "''").replace(/\s+/g, ' ')
: status.trim().replace(/\s+/g, ' ');
// jika searching status terdapat dalam enum, maka dia mencari specific data
// ? karena jika tidak, ketika dia search "active" maka "inactive" juga ikut
return STATUS[statusData.toUpperCase()] ?? statusData;
});
specificFilter.push({
cols: `${this.tableName}.status::text`,
data: data,
});
}
new SpecificSearchFilter<Entity>(
this.queryBuilder,
this.tableName,
this.specificFilter,
specificFilter,
).getFilter();
getOrderBy(this.filterParam, this.queryBuilder, this.tableName);

View File

@ -9,6 +9,7 @@ export abstract class BaseUpdateStatusManager<Entity> extends BaseManager {
protected result: Entity;
protected oldData: Entity;
protected dataStatus: STATUS;
protected relations = [];
protected duplicateColumn: string[];
abstract get entityTarget(): any;
@ -22,6 +23,7 @@ export abstract class BaseUpdateStatusManager<Entity> extends BaseManager {
where: {
id: this.dataId,
},
relations: this.relations,
});
this.oldData = _.cloneDeep(this.data);

View File

@ -20,6 +20,18 @@ export abstract class BaseUpdateManager<Entity> extends BaseManager {
}
async prepareData(): Promise<void> {
this.oldData = await this.dataService.getOneByOptions({
where: { id: this.dataId },
});
if (!this.oldData) {
throw new NotFoundException({
statusCode: HttpStatus.NOT_FOUND,
message: `Failed! Entity with id ${this.dataId} not found`,
error: 'Entity Not Found',
});
}
Object.assign(this.data, {
editor_id: this.user.id,
editor_name: this.user.name,
@ -38,18 +50,6 @@ export abstract class BaseUpdateManager<Entity> extends BaseManager {
}
async process(): Promise<void> {
this.oldData = await this.dataService.getOneByOptions({
where: { id: this.dataId },
});
if (!this.oldData) {
throw new NotFoundException({
statusCode: HttpStatus.NOT_FOUND,
message: `Failed! Entity with id ${this.dataId} not found`,
error: 'Entity Not Found',
});
}
await new ValidateRelationHelper(
this.dataId,
this.dataService,

View File

@ -4,9 +4,9 @@ import { BaseDataOrchestrator } from './base-data.orchestrator';
export abstract class BaseDataTransactionOrchestrator<
Entity,
> extends BaseDataOrchestrator<Entity> {
abstract active(dataId: string): Promise<String>;
abstract confirm(dataId: string): Promise<String>;
abstract inactive(dataId: string): Promise<String>;
abstract active(dataId: string): Promise<string>;
abstract confirm(dataId: string): Promise<string>;
abstract inactive(dataId: string): Promise<string>;
abstract batchConfirm(dataIds: string[]): Promise<BatchResult>;
abstract batchActive(dataIds: string[]): Promise<BatchResult>;
abstract batchInactive(dataIds: string[]): Promise<BatchResult>;

View File

@ -3,6 +3,6 @@ import { BatchResult } from 'src/core/response/domain/ok-response.interface';
export abstract class BaseDataOrchestrator<Entity> {
abstract create(data: Entity): Promise<Entity>;
abstract update(dataId: string, data: Entity): Promise<Entity>;
abstract delete(dataId: string): Promise<String>;
abstract delete(dataId: string): Promise<string>;
abstract batchDelete(dataIds: string[]): Promise<BatchResult>;
}

View File

@ -0,0 +1,4 @@
export class ChangePositionDto {
start: string;
end: string;
}

View File

@ -6,7 +6,9 @@ export enum STATUS {
DRAFT = 'draft',
EXPIRED = 'expired',
INACTIVE = 'inactive',
PARTIAL_REFUND = 'partial refund',
PENDING = 'pending',
PROCESS_REFUND = 'proses refund',
REFUNDED = 'refunded',
REJECTED = 'rejected',
SETTLED = 'settled',
@ -46,3 +48,5 @@ export const BLANK_USER = {
role: null,
user_privilege_id: null,
};
export const EMPTY_UUID = '00000000-0000-0000-0000-000000000000';

View File

@ -24,7 +24,7 @@ export interface validateRelations {
export interface columnUniques {
column: string;
query?: Object;
query?: any;
}
export interface IEvent<Entity = any> {

View File

@ -1,15 +1,27 @@
export enum MODULE_NAME {
BANNER = 'banners',
FAQ = 'faqs',
GATE = 'gates',
ITEM = 'items',
ITEM_CATEGORY = 'item-categories',
ITEM_RATE = 'item-rates',
NEWS = 'news',
PAYMENT_METHOD = 'payment-methods',
RECONCILIATION = 'reconciliations',
REFUND = 'refunds',
SEASON_TYPE = 'season-types',
SEASON_PERIOD = 'season-periods',
TAX = 'taxes',
TERM_CONDITION = 'term_conditions',
TENANT = 'tenants',
TRANSACTION = 'transactions',
USER = 'users',
USER_PRIVILEGE = 'user-privileges',
USER_PRIVILEGE_CONFIGURATION = 'user-privilege-configurations',
VIP_CATEGORY = 'vip-categories',
VIP_CODE = 'vip-codes',
REPORT = 'report',
REPORT_BOOKMARK = 'report-bookmark',
REPORT_EXPORT = 'report-export',
}

View File

@ -47,7 +47,7 @@ export const PrivilegeAdminConstant = [
index: 4,
},
{
menu: 'REKONSILIASI',
menu: 'RECONCILIATION',
menu_label: 'Rekonsiliasi',
actions: [
PrivilegeAction.VIEW,
@ -126,13 +126,13 @@ export const PrivilegeAdminConstant = [
index: 11,
},
{
menu: 'LAPORAN',
menu: 'REPORT',
menu_label: 'Laporan',
actions: [PrivilegeAction.VIEW],
index: 12,
},
{
menu: 'DISKON_CODE',
menu: 'DISCOUNT_CODE',
menu_label: 'Generate Diskon Kode',
actions: [PrivilegeAction.CREATE],
index: 13,
@ -170,7 +170,7 @@ export const PrivilegePOSConstant = [
index: 17,
},
{
menu: 'POS_DISKON_CODE',
menu: 'POS_DISCOUNT_CODE',
menu_label: 'Generate Diskon Kode',
actions: [PrivilegeAction.CREATE],
index: 18,

View File

@ -1,15 +1,25 @@
export enum TABLE_NAME {
BANNER = 'banners',
ERROR_LOG = 'log_errors',
FAQ = 'faqs',
ITEM = 'items',
ITEM_CATEGORY = 'item_categories',
ITEM_RATE = 'item_rates',
GATE = 'gates',
LOG = 'logs',
NEWS = 'news',
PAYMENT_METHOD = 'payment_methods',
PRICE_FORMULA = 'price_formulas',
REFUND = 'refunds',
REFUND_ITEM = 'refund_items',
SEASON_TYPE = 'season_types',
SEASON_PERIOD = 'season_periods',
TAX = 'taxes',
TERM_CONDITION = 'term_conditions',
TENANT = 'tenants',
TRANSACTION = 'transactions',
TRANSACTION_ITEM = 'transaction_items',
TRANSACTION_TAX = 'transaction_taxes',
USER = 'users',
USER_PRIVILEGE = 'user_privileges',
USER_PRIVILEGE_CONFIGURATION = 'user_privilege_configurations',

View File

@ -9,7 +9,7 @@ import { MODULE_NAME } from 'src/core/strings/constants/module.constants';
import { Public } from 'src/core/guards';
@ApiTags(`${MODULE_NAME.{{constantCase name}}.split('-').join(' ')} - read`)
@Controller(`v1/${MODULE_NAME.{{constantCase name}}}`)
@Controller(`v1/${MODULE_NAME.{{constantCase name}} }`)
@Public(false)
@ApiBearerAuth('JWT')
export class {{pascalCase name}}ReadController {

View File

@ -17,7 +17,7 @@ import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto'
import { Public } from 'src/core/guards';
@ApiTags(`${MODULE_NAME.{{constantCase name}}.split('-').join(' ')} - data`)
@Controller(`v1/${MODULE_NAME.{{constantCase name}}}`)
@Controller(`v1/${MODULE_NAME.{{constantCase name}} }`)
@Public(false)
@ApiBearerAuth('JWT')
export class {{pascalCase name}}DataController {
@ -36,7 +36,7 @@ export class {{pascalCase name}}DataController {
}
@Patch(':id/active')
async active(@Param('id') dataId: string): Promise<String> {
async active(@Param('id') dataId: string): Promise<string> {
return await this.orchestrator.active(dataId);
}
@ -46,7 +46,7 @@ export class {{pascalCase name}}DataController {
}
@Patch(':id/confirm')
async confirm(@Param('id') dataId: string): Promise<String> {
async confirm(@Param('id') dataId: string): Promise<string> {
return await this.orchestrator.confirm(dataId);
}
@ -56,7 +56,7 @@ export class {{pascalCase name}}DataController {
}
@Patch(':id/inactive')
async inactive(@Param('id') dataId: string): Promise<String> {
async inactive(@Param('id') dataId: string): Promise<string> {
return await this.orchestrator.inactive(dataId);
}
@ -74,7 +74,7 @@ export class {{pascalCase name}}DataController {
}
@Delete(':id')
async delete(@Param('id') dataId: string): Promise<String> {
async delete(@Param('id') dataId: string): Promise<string> {
return await this.orchestrator.delete(dataId);
}
}

View File

@ -16,7 +16,7 @@ import {
import { Public } from 'src/core/guards';
@ApiTags(`${MODULE_NAME.{{constantCase name}}.split('-').join(' ')} - data`)
@Controller(`v1/${MODULE_NAME.{{constantCase name}}}`)
@Controller(`v1/${MODULE_NAME.{{constantCase name}} }`)
@Public(false)
@ApiBearerAuth('JWT')
export class {{pascalCase name}}DataController {
@ -43,7 +43,7 @@ import {
}
@Delete(':id')
async delete(@Param('id') dataId: string): Promise<String> {
async delete(@Param('id') dataId: string): Promise<string> {
return await this.orchestrator.delete(dataId);
}
}

View File

@ -48,7 +48,7 @@ export class {{pascalCase name}}DataOrchestrator extends Base{{pascalCase orches
return this.updateManager.getResult();
}
async delete(dataId): Promise<String> {
async delete(dataId): Promise<string> {
this.deleteManager.setData(dataId);
this.deleteManager.setService(this.serviceData, TABLE_NAME.{{constantCase name}});
await this.deleteManager.execute();
@ -65,7 +65,7 @@ export class {{pascalCase name}}DataOrchestrator extends Base{{pascalCase orches
return this.batchDeleteManager.getResult();
}
async active(dataId): Promise<String> {
async active(dataId): Promise<string> {
this.activeManager.setData(dataId, STATUS.ACTIVE);
this.activeManager.setService(this.serviceData, TABLE_NAME.{{constantCase name}});
await this.activeManager.execute();
@ -82,7 +82,7 @@ export class {{pascalCase name}}DataOrchestrator extends Base{{pascalCase orches
return this.batchActiveManager.getResult();
}
async confirm(dataId): Promise<String> {
async confirm(dataId): Promise<string> {
this.confirmManager.setData(dataId, STATUS.ACTIVE);
this.confirmManager.setService(this.serviceData, TABLE_NAME.{{constantCase name}});
await this.confirmManager.execute();
@ -99,7 +99,7 @@ export class {{pascalCase name}}DataOrchestrator extends Base{{pascalCase orches
return this.batchConfirmManager.getResult();
}
async inactive(dataId): Promise<String> {
async inactive(dataId): Promise<string> {
this.inactiveManager.setData(dataId, STATUS.INACTIVE);
this.inactiveManager.setService(
this.serviceData,

View File

@ -36,7 +36,7 @@ export class {{pascalCase name}}DataOrchestrator extends Base{{pascalCase orches
return this.updateManager.getResult();
}
async delete(dataId): Promise<String> {
async delete(dataId): Promise<string> {
this.deleteManager.setData(dataId);
this.deleteManager.setService(this.serviceData, TABLE_NAME.{{constantCase name}});
await this.deleteManager.execute();

View File

@ -0,0 +1,55 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class Transaction1719572714752 implements MigrationInterface {
name = 'Transaction1719572714752';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TABLE "transaction_items" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "item_id" character varying, "item_name" character varying, "item_type" character varying, "item_price" bigint, "item_tenant_id" character varying, "item_tenant_name" character varying, "item_tenant_share_margin" numeric, "total_price" numeric, "total_hpp" numeric, "total_profit" numeric, "total_share_tenant" numeric, "qty" integer, "transaction_id" uuid, CONSTRAINT "PK_ff5a487ad820dccafd53bebf578" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "transaction_taxes" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "tax_id" character varying, "tax_name" character varying, "taxt_value" character varying, "transaction_id" uuid, CONSTRAINT "PK_208b2abdb10640e1991972fc754" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_type_enum" AS ENUM('counter', 'admin', 'online')`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_customer_type_enum" AS ENUM('group', 'vip')`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_payment_type_enum" AS ENUM('midtrans', 'bank transfer', 'qris', 'counter', 'cash', 'credit card', 'debit', 'e-money')`,
);
await queryRunner.query(
`CREATE TABLE "transactions" ("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, "status" "public"."transactions_status_enum" NOT NULL DEFAULT 'draft', "type" "public"."transactions_type_enum" NOT NULL DEFAULT 'admin', "invoice_code" character varying, "creator_counter_no" integer, "season_period_id" character varying, "season_period_name" character varying, "season_period_type_id" character varying, "season_period_type_name" character varying, "customer_type" "public"."transactions_customer_type_enum", "customer_category_id" character varying, "customer_category_name" character varying, "customer_name" character varying, "customer_phone" character varying, "customer_email" character varying, "customer_description" character varying, "no_of_group" character varying, "booking_date" date, "settlement_date" date, "invoice_date" date, "discount_code_id" character varying, "discount_code" character varying, "discount_percentage" integer, "discount_value" numeric, "payment_type" "public"."transactions_payment_type_enum" NOT NULL DEFAULT 'bank transfer', "payment_type_method_id" character varying, "payment_type_method_name" character varying, "payment_type_method_qr" character varying, "payment_card_information" character varying, "payment_code_reference" character varying, "payment_date" date, "payment_sub_total" numeric, "payment_discount_total" numeric, "payment_total" numeric, "payment_total_pay" numeric, "payment_change" numeric, "payment_total_share" numeric, "payment_total_tax" numeric, "payment_total_profit" numeric, "profit_share_formula" character varying, "sales_price_formula" character varying, CONSTRAINT "PK_a219afd8dd77ed80f5a862f1db9" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" ADD CONSTRAINT "FK_5926425896b30c0d681fe879af0" FOREIGN KEY ("transaction_id") REFERENCES "transactions"("id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
await queryRunner.query(
`ALTER TABLE "transaction_taxes" ADD CONSTRAINT "FK_d21db1756c6656efc7c082fbaa6" FOREIGN KEY ("transaction_id") REFERENCES "transactions"("id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transaction_taxes" DROP CONSTRAINT "FK_d21db1756c6656efc7c082fbaa6"`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" DROP CONSTRAINT "FK_5926425896b30c0d681fe879af0"`,
);
await queryRunner.query(`DROP TABLE "transactions"`);
await queryRunner.query(
`DROP TYPE "public"."transactions_payment_type_enum"`,
);
await queryRunner.query(
`DROP TYPE "public"."transactions_customer_type_enum"`,
);
await queryRunner.query(`DROP TYPE "public"."transactions_type_enum"`);
await queryRunner.query(`DROP TYPE "public"."transactions_status_enum"`);
await queryRunner.query(`DROP TABLE "transaction_taxes"`);
await queryRunner.query(`DROP TABLE "transaction_items"`);
}
}

View File

@ -0,0 +1,61 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddReconciliationToTransaction1719925690145
implements MigrationInterface
{
name = 'AddReconciliationToTransaction1719925690145';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transactions" ADD "is_recap_transaction" boolean NOT NULL DEFAULT true`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "payment_type_method_number" character varying`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "reconciliation_mdr" numeric`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_reconciliation_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "reconciliation_status" "public"."transactions_reconciliation_status_enum" NOT NULL DEFAULT 'draft'`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "reconciliation_confirm_date" character varying`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "reconciliation_confirm_by" character varying`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "payment_total_net_profit" numeric`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "payment_total_net_profit"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "reconciliation_confirm_by"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "reconciliation_confirm_date"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "reconciliation_status"`,
);
await queryRunner.query(
`DROP TYPE "public"."transactions_reconciliation_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "reconciliation_mdr"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "payment_type_method_number"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "is_recap_transaction"`,
);
}
}

View File

@ -0,0 +1,71 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class UpdateTableTransaction1719934464407 implements MigrationInterface {
name = 'UpdateTableTransaction1719934464407';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transaction_items" ADD "item_hpp" bigint`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" ADD "item_category_id" character varying`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" ADD "item_category_name" character varying`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" ADD "item_bundlings" json`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_sending_invoice_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "sending_invoice_status" "public"."transactions_sending_invoice_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "sending_invoice_at" bigint`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_sending_qr_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "sending_qr_status" "public"."transactions_sending_qr_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "sending_qr_at" bigint`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "sending_qr_at"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "sending_qr_status"`,
);
await queryRunner.query(
`DROP TYPE "public"."transactions_sending_qr_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "sending_invoice_at"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "sending_invoice_status"`,
);
await queryRunner.query(
`DROP TYPE "public"."transactions_sending_invoice_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" DROP COLUMN "item_bundlings"`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" DROP COLUMN "item_category_name"`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" DROP COLUMN "item_category_id"`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" DROP COLUMN "item_hpp"`,
);
}
}

View File

@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddColumnTypeReportBookmark1719982860855
implements MigrationInterface
{
name = 'AddColumnTypeReportBookmark1719982860855';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TYPE "public"."report_bookmark_type_enum" AS ENUM('TABLE_CONFIG', 'FILTER_TABLE')`,
);
await queryRunner.query(
`ALTER TABLE "report_bookmark" ADD "type" "public"."report_bookmark_type_enum" NOT NULL DEFAULT 'TABLE_CONFIG'`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "report_bookmark" DROP COLUMN "type"`);
await queryRunner.query(`DROP TYPE "public"."report_bookmark_type_enum"`);
}
}

View File

@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class UpdateDefaultColumnTransaction1720077765890
implements MigrationInterface
{
name = 'UpdateDefaultColumnTransaction1720077765890';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "is_recap_transaction" SET DEFAULT false`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "is_recap_transaction" SET DEFAULT true`,
);
}
}

View File

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

View File

@ -0,0 +1,27 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class UpdateTableTransaction1720767689625 implements MigrationInterface {
name = 'UpdateTableTransaction1720767689625';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transaction_items" ADD "qty_remaining" integer`,
);
await queryRunner.query(`ALTER TABLE "transaction_items" ADD "taxes" json`);
await queryRunner.query(
`ALTER TABLE "transactions" ADD "payment_total_dpp" numeric`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "transactions" DROP COLUMN "payment_total_dpp"`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" DROP COLUMN "taxes"`,
);
await queryRunner.query(
`ALTER TABLE "transaction_items" DROP COLUMN "qty_remaining"`,
);
}
}

View File

@ -0,0 +1,45 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class Refund1720768975877 implements MigrationInterface {
name = 'Refund1720768975877';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TYPE "public"."refunds_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`CREATE TYPE "public"."refunds_type_enum" AS ENUM('pengembalian booking', 'pengembalian wahana')`,
);
await queryRunner.query(
`CREATE TABLE "refunds" ("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, "status" "public"."refunds_status_enum" NOT NULL DEFAULT 'draft', "type" "public"."refunds_type_enum" NOT NULL DEFAULT 'pengembalian booking', "code" character varying, "request_date" date, "refund_date" date, "refund_total" numeric, "bank_name" character varying, "bank_account_name" character varying, "bank_account_number" character varying, "transaction_id" uuid, CONSTRAINT "REL_8bb3b7579f49990d2e77684acd" UNIQUE ("transaction_id"), CONSTRAINT "PK_5106efb01eeda7e49a78b869738" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`CREATE TABLE "refund_items" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "qty_refund" numeric, "refund_total" numeric, "refund_item_id" uuid, "transaction_item_id" uuid, CONSTRAINT "REL_07b481a163c219f5de8fb1c90b" UNIQUE ("transaction_item_id"), CONSTRAINT "PK_ef892918375a6101948b90f1140" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`ALTER TABLE "refunds" ADD CONSTRAINT "FK_8bb3b7579f49990d2e77684acd4" FOREIGN KEY ("transaction_id") REFERENCES "transactions"("id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
await queryRunner.query(
`ALTER TABLE "refund_items" ADD CONSTRAINT "FK_2a4bd60fb8a9c37f902f4f3da67" FOREIGN KEY ("refund_item_id") REFERENCES "refunds"("id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
await queryRunner.query(
`ALTER TABLE "refund_items" ADD CONSTRAINT "FK_07b481a163c219f5de8fb1c90b3" FOREIGN KEY ("transaction_item_id") REFERENCES "transaction_items"("id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "refund_items" DROP CONSTRAINT "FK_07b481a163c219f5de8fb1c90b3"`,
);
await queryRunner.query(
`ALTER TABLE "refund_items" DROP CONSTRAINT "FK_2a4bd60fb8a9c37f902f4f3da67"`,
);
await queryRunner.query(
`ALTER TABLE "refunds" DROP CONSTRAINT "FK_8bb3b7579f49990d2e77684acd4"`,
);
await queryRunner.query(`DROP TABLE "refund_items"`);
await queryRunner.query(`DROP TABLE "refunds"`);
await queryRunner.query(`DROP TYPE "public"."refunds_type_enum"`);
await queryRunner.query(`DROP TYPE "public"."refunds_status_enum"`);
}
}

View File

@ -0,0 +1,461 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class UpdateEnumStatus1720774145470 implements MigrationInterface {
name = 'UpdateEnumStatus1720774145470';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TYPE "public"."item_categories_status_enum" RENAME TO "item_categories_status_enum_old"`,
);
await queryRunner.query(
`CREATE TYPE "public"."item_categories_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "item_categories" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "item_categories" ALTER COLUMN "status" TYPE "public"."item_categories_status_enum" USING "status"::"text"::"public"."item_categories_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "item_categories" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(
`DROP TYPE "public"."item_categories_status_enum_old"`,
);
await queryRunner.query(
`ALTER TYPE "public"."season_types_status_enum" RENAME TO "season_types_status_enum_old"`,
);
await queryRunner.query(
`CREATE TYPE "public"."season_types_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "season_types" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "season_types" ALTER COLUMN "status" TYPE "public"."season_types_status_enum" USING "status"::"text"::"public"."season_types_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "season_types" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(
`DROP TYPE "public"."season_types_status_enum_old"`,
);
await queryRunner.query(
`ALTER TYPE "public"."season_periods_status_enum" RENAME TO "season_periods_status_enum_old"`,
);
await queryRunner.query(
`CREATE TYPE "public"."season_periods_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "season_periods" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "season_periods" ALTER COLUMN "status" TYPE "public"."season_periods_status_enum" USING "status"::"text"::"public"."season_periods_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "season_periods" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(
`DROP TYPE "public"."season_periods_status_enum_old"`,
);
await queryRunner.query(
`ALTER TYPE "public"."items_status_enum" RENAME TO "items_status_enum_old"`,
);
await queryRunner.query(
`CREATE TYPE "public"."items_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "items" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "items" ALTER COLUMN "status" TYPE "public"."items_status_enum" USING "status"::"text"::"public"."items_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "items" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(`DROP TYPE "public"."items_status_enum_old"`);
await queryRunner.query(
`ALTER TYPE "public"."users_status_enum" RENAME TO "users_status_enum_old"`,
);
await queryRunner.query(
`CREATE TYPE "public"."users_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "users" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "users" ALTER COLUMN "status" TYPE "public"."users_status_enum" USING "status"::"text"::"public"."users_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "users" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(`DROP TYPE "public"."users_status_enum_old"`);
await queryRunner.query(
`ALTER TYPE "public"."user_privileges_status_enum" RENAME TO "user_privileges_status_enum_old"`,
);
await queryRunner.query(
`CREATE TYPE "public"."user_privileges_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "user_privileges" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "user_privileges" ALTER COLUMN "status" TYPE "public"."user_privileges_status_enum" USING "status"::"text"::"public"."user_privileges_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "user_privileges" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(
`DROP TYPE "public"."user_privileges_status_enum_old"`,
);
await queryRunner.query(
`ALTER TYPE "public"."refunds_status_enum" RENAME TO "refunds_status_enum_old"`,
);
await queryRunner.query(
`CREATE TYPE "public"."refunds_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "refunds" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "refunds" ALTER COLUMN "status" TYPE "public"."refunds_status_enum" USING "status"::"text"::"public"."refunds_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "refunds" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(`DROP TYPE "public"."refunds_status_enum_old"`);
await queryRunner.query(
`ALTER TYPE "public"."transactions_status_enum" RENAME TO "transactions_status_enum_old"`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "status" TYPE "public"."transactions_status_enum" USING "status"::"text"::"public"."transactions_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(
`DROP TYPE "public"."transactions_status_enum_old"`,
);
await queryRunner.query(
`ALTER TYPE "public"."transactions_reconciliation_status_enum" RENAME TO "transactions_reconciliation_status_enum_old"`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_reconciliation_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" TYPE "public"."transactions_reconciliation_status_enum" USING "reconciliation_status"::"text"::"public"."transactions_reconciliation_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" SET DEFAULT 'draft'`,
);
await queryRunner.query(
`DROP TYPE "public"."transactions_reconciliation_status_enum_old"`,
);
await queryRunner.query(
`ALTER TYPE "public"."transactions_sending_invoice_status_enum" RENAME TO "transactions_sending_invoice_status_enum_old"`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_sending_invoice_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "sending_invoice_status" TYPE "public"."transactions_sending_invoice_status_enum" USING "sending_invoice_status"::"text"::"public"."transactions_sending_invoice_status_enum"`,
);
await queryRunner.query(
`DROP TYPE "public"."transactions_sending_invoice_status_enum_old"`,
);
await queryRunner.query(
`ALTER TYPE "public"."transactions_sending_qr_status_enum" RENAME TO "transactions_sending_qr_status_enum_old"`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_sending_qr_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "sending_qr_status" TYPE "public"."transactions_sending_qr_status_enum" USING "sending_qr_status"::"text"::"public"."transactions_sending_qr_status_enum"`,
);
await queryRunner.query(
`DROP TYPE "public"."transactions_sending_qr_status_enum_old"`,
);
await queryRunner.query(
`ALTER TYPE "public"."vip_categories_status_enum" RENAME TO "vip_categories_status_enum_old"`,
);
await queryRunner.query(
`CREATE TYPE "public"."vip_categories_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "vip_categories" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "vip_categories" ALTER COLUMN "status" TYPE "public"."vip_categories_status_enum" USING "status"::"text"::"public"."vip_categories_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "vip_categories" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(
`DROP TYPE "public"."vip_categories_status_enum_old"`,
);
await queryRunner.query(
`ALTER TYPE "public"."taxes_status_enum" RENAME TO "taxes_status_enum_old"`,
);
await queryRunner.query(
`CREATE TYPE "public"."taxes_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "taxes" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "taxes" ALTER COLUMN "status" TYPE "public"."taxes_status_enum" USING "status"::"text"::"public"."taxes_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "taxes" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(`DROP TYPE "public"."taxes_status_enum_old"`);
await queryRunner.query(
`ALTER TYPE "public"."payment_methods_status_enum" RENAME TO "payment_methods_status_enum_old"`,
);
await queryRunner.query(
`CREATE TYPE "public"."payment_methods_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "payment_methods" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "payment_methods" ALTER COLUMN "status" TYPE "public"."payment_methods_status_enum" USING "status"::"text"::"public"."payment_methods_status_enum"`,
);
await queryRunner.query(
`ALTER TABLE "payment_methods" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(
`DROP TYPE "public"."payment_methods_status_enum_old"`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TYPE "public"."payment_methods_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "payment_methods" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "payment_methods" ALTER COLUMN "status" TYPE "public"."payment_methods_status_enum_old" USING "status"::"text"::"public"."payment_methods_status_enum_old"`,
);
await queryRunner.query(
`ALTER TABLE "payment_methods" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(`DROP TYPE "public"."payment_methods_status_enum"`);
await queryRunner.query(
`ALTER TYPE "public"."payment_methods_status_enum_old" RENAME TO "payment_methods_status_enum"`,
);
await queryRunner.query(
`CREATE TYPE "public"."taxes_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "taxes" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "taxes" ALTER COLUMN "status" TYPE "public"."taxes_status_enum_old" USING "status"::"text"::"public"."taxes_status_enum_old"`,
);
await queryRunner.query(
`ALTER TABLE "taxes" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(`DROP TYPE "public"."taxes_status_enum"`);
await queryRunner.query(
`ALTER TYPE "public"."taxes_status_enum_old" RENAME TO "taxes_status_enum"`,
);
await queryRunner.query(
`CREATE TYPE "public"."vip_categories_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "vip_categories" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "vip_categories" ALTER COLUMN "status" TYPE "public"."vip_categories_status_enum_old" USING "status"::"text"::"public"."vip_categories_status_enum_old"`,
);
await queryRunner.query(
`ALTER TABLE "vip_categories" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(`DROP TYPE "public"."vip_categories_status_enum"`);
await queryRunner.query(
`ALTER TYPE "public"."vip_categories_status_enum_old" RENAME TO "vip_categories_status_enum"`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_sending_qr_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "sending_qr_status" TYPE "public"."transactions_sending_qr_status_enum_old" USING "sending_qr_status"::"text"::"public"."transactions_sending_qr_status_enum_old"`,
);
await queryRunner.query(
`DROP TYPE "public"."transactions_sending_qr_status_enum"`,
);
await queryRunner.query(
`ALTER TYPE "public"."transactions_sending_qr_status_enum_old" RENAME TO "transactions_sending_qr_status_enum"`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_sending_invoice_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "sending_invoice_status" TYPE "public"."transactions_sending_invoice_status_enum_old" USING "sending_invoice_status"::"text"::"public"."transactions_sending_invoice_status_enum_old"`,
);
await queryRunner.query(
`DROP TYPE "public"."transactions_sending_invoice_status_enum"`,
);
await queryRunner.query(
`ALTER TYPE "public"."transactions_sending_invoice_status_enum_old" RENAME TO "transactions_sending_invoice_status_enum"`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_reconciliation_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" TYPE "public"."transactions_reconciliation_status_enum_old" USING "reconciliation_status"::"text"::"public"."transactions_reconciliation_status_enum_old"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "reconciliation_status" SET DEFAULT 'draft'`,
);
await queryRunner.query(
`DROP TYPE "public"."transactions_reconciliation_status_enum"`,
);
await queryRunner.query(
`ALTER TYPE "public"."transactions_reconciliation_status_enum_old" RENAME TO "transactions_reconciliation_status_enum"`,
);
await queryRunner.query(
`CREATE TYPE "public"."transactions_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "status" TYPE "public"."transactions_status_enum_old" USING "status"::"text"::"public"."transactions_status_enum_old"`,
);
await queryRunner.query(
`ALTER TABLE "transactions" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(`DROP TYPE "public"."transactions_status_enum"`);
await queryRunner.query(
`ALTER TYPE "public"."transactions_status_enum_old" RENAME TO "transactions_status_enum"`,
);
await queryRunner.query(
`CREATE TYPE "public"."refunds_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "refunds" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "refunds" ALTER COLUMN "status" TYPE "public"."refunds_status_enum_old" USING "status"::"text"::"public"."refunds_status_enum_old"`,
);
await queryRunner.query(
`ALTER TABLE "refunds" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(`DROP TYPE "public"."refunds_status_enum"`);
await queryRunner.query(
`ALTER TYPE "public"."refunds_status_enum_old" RENAME TO "refunds_status_enum"`,
);
await queryRunner.query(
`CREATE TYPE "public"."user_privileges_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "user_privileges" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "user_privileges" ALTER COLUMN "status" TYPE "public"."user_privileges_status_enum_old" USING "status"::"text"::"public"."user_privileges_status_enum_old"`,
);
await queryRunner.query(
`ALTER TABLE "user_privileges" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(`DROP TYPE "public"."user_privileges_status_enum"`);
await queryRunner.query(
`ALTER TYPE "public"."user_privileges_status_enum_old" RENAME TO "user_privileges_status_enum"`,
);
await queryRunner.query(
`CREATE TYPE "public"."users_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "users" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "users" ALTER COLUMN "status" TYPE "public"."users_status_enum_old" USING "status"::"text"::"public"."users_status_enum_old"`,
);
await queryRunner.query(
`ALTER TABLE "users" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(`DROP TYPE "public"."users_status_enum"`);
await queryRunner.query(
`ALTER TYPE "public"."users_status_enum_old" RENAME TO "users_status_enum"`,
);
await queryRunner.query(
`CREATE TYPE "public"."items_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "items" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "items" ALTER COLUMN "status" TYPE "public"."items_status_enum_old" USING "status"::"text"::"public"."items_status_enum_old"`,
);
await queryRunner.query(
`ALTER TABLE "items" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(`DROP TYPE "public"."items_status_enum"`);
await queryRunner.query(
`ALTER TYPE "public"."items_status_enum_old" RENAME TO "items_status_enum"`,
);
await queryRunner.query(
`CREATE TYPE "public"."season_periods_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "season_periods" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "season_periods" ALTER COLUMN "status" TYPE "public"."season_periods_status_enum_old" USING "status"::"text"::"public"."season_periods_status_enum_old"`,
);
await queryRunner.query(
`ALTER TABLE "season_periods" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(`DROP TYPE "public"."season_periods_status_enum"`);
await queryRunner.query(
`ALTER TYPE "public"."season_periods_status_enum_old" RENAME TO "season_periods_status_enum"`,
);
await queryRunner.query(
`CREATE TYPE "public"."season_types_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "season_types" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "season_types" ALTER COLUMN "status" TYPE "public"."season_types_status_enum_old" USING "status"::"text"::"public"."season_types_status_enum_old"`,
);
await queryRunner.query(
`ALTER TABLE "season_types" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(`DROP TYPE "public"."season_types_status_enum"`);
await queryRunner.query(
`ALTER TYPE "public"."season_types_status_enum_old" RENAME TO "season_types_status_enum"`,
);
await queryRunner.query(
`CREATE TYPE "public"."item_categories_status_enum_old" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'pending', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`ALTER TABLE "item_categories" ALTER COLUMN "status" DROP DEFAULT`,
);
await queryRunner.query(
`ALTER TABLE "item_categories" ALTER COLUMN "status" TYPE "public"."item_categories_status_enum_old" USING "status"::"text"::"public"."item_categories_status_enum_old"`,
);
await queryRunner.query(
`ALTER TABLE "item_categories" ALTER COLUMN "status" SET DEFAULT 'draft'`,
);
await queryRunner.query(`DROP TYPE "public"."item_categories_status_enum"`);
await queryRunner.query(
`ALTER TYPE "public"."item_categories_status_enum_old" RENAME TO "item_categories_status_enum"`,
);
}
}

View File

@ -0,0 +1,29 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class Gate1721024987609 implements MigrationInterface {
name = 'Gate1721024987609';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TYPE "public"."gates_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`CREATE TYPE "public"."gates_type_enum" AS ENUM('gate masuk', 'gate keluar')`,
);
await queryRunner.query(
`CREATE TABLE "gates" ("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, "status" "public"."gates_status_enum" NOT NULL DEFAULT 'draft', "type" "public"."gates_type_enum" NOT NULL DEFAULT 'gate masuk', "code" character varying, "note" text, "item_id" uuid, CONSTRAINT "PK_2dd58a77462dd2c5695ec4a7975" PRIMARY KEY ("id"))`,
);
await queryRunner.query(
`ALTER TABLE "gates" ADD CONSTRAINT "FK_29f020cd153bb079722bcbee830" FOREIGN KEY ("item_id") REFERENCES "items"("id") ON DELETE CASCADE ON UPDATE CASCADE`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "gates" DROP CONSTRAINT "FK_29f020cd153bb079722bcbee830"`,
);
await queryRunner.query(`DROP TABLE "gates"`);
await queryRunner.query(`DROP TYPE "public"."gates_type_enum"`);
await queryRunner.query(`DROP TYPE "public"."gates_status_enum"`);
}
}

View File

@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class TermCondition1721029248635 implements MigrationInterface {
name = 'TermCondition1721029248635';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TYPE "public"."term_conditions_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`CREATE TABLE "term_conditions" ("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, "status" "public"."term_conditions_status_enum" NOT NULL DEFAULT 'draft', "title" character varying, "description" text, CONSTRAINT "PK_fc92769e487820f24ed68337feb" PRIMARY KEY ("id"))`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "term_conditions"`);
await queryRunner.query(`DROP TYPE "public"."term_conditions_status_enum"`);
}
}

View File

@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class FrequentlyAskQuestion1721029454627 implements MigrationInterface {
name = 'FrequentlyAskQuestion1721029454627';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TYPE "public"."faqs_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`CREATE TABLE "faqs" ("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, "status" "public"."faqs_status_enum" NOT NULL DEFAULT 'draft', "title" character varying, "description" text, CONSTRAINT "PK_2ddf4f2c910f8e8fa2663a67bf0" PRIMARY KEY ("id"))`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "faqs"`);
await queryRunner.query(`DROP TYPE "public"."faqs_status_enum"`);
}
}

View File

@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class UpdateRefund1721031712642 implements MigrationInterface {
name = 'UpdateRefund1721031712642';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "refunds" ADD "refund_sub_total" numeric`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "refunds" DROP COLUMN "refund_sub_total"`,
);
}
}

View File

@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class News1721109817371 implements MigrationInterface {
name = 'News1721109817371';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TYPE "public"."news_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`CREATE TABLE "news" ("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, "status" "public"."news_status_enum" NOT NULL DEFAULT 'draft', "image_url" character varying, "title" character varying, "teaser" character varying, "description" character varying, CONSTRAINT "PK_39a43dfcb6007180f04aff2357e" PRIMARY KEY ("id"))`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "news"`);
await queryRunner.query(`DROP TYPE "public"."news_status_enum"`);
}
}

View File

@ -0,0 +1,19 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class Banner1721111093665 implements MigrationInterface {
name = 'Banner1721111093665';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`CREATE TYPE "public"."banners_status_enum" AS ENUM('active', 'cancel', 'confirmed', 'draft', 'expired', 'inactive', 'partial refund', 'pending', 'proses refund', 'refunded', 'rejected', 'settled', 'waiting')`,
);
await queryRunner.query(
`CREATE TABLE "banners" ("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, "status" "public"."banners_status_enum" NOT NULL DEFAULT 'draft', "image_url" character varying, "title" character varying, "link" character varying, CONSTRAINT "PK_e9b186b959296fcb940790d31c3" PRIMARY KEY ("id"))`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE "banners"`);
await queryRunner.query(`DROP TYPE "public"."banners_status_enum"`);
}
}

View File

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

View File

@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class UpdateSortColumn1721284172572 implements MigrationInterface {
name = 'UpdateSortColumn1721284172572';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "term_conditions" ADD "sort_order" integer NOT NULL DEFAULT '0'`,
);
await queryRunner.query(
`ALTER TABLE "faqs" ADD "sort_order" integer NOT NULL DEFAULT '0'`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "faqs" DROP COLUMN "sort_order"`);
await queryRunner.query(
`ALTER TABLE "term_conditions" DROP COLUMN "sort_order"`,
);
}
}

View File

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

View File

@ -0,0 +1,17 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class UpdateImageColumnItem1721647955446 implements MigrationInterface {
name = 'UpdateImageColumnItem1721647955446';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "items" RENAME COLUMN "image" TO "image_url"`,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "items" RENAME COLUMN "image_url" TO "image"`,
);
}
}

View File

@ -29,8 +29,6 @@ export class SeedDefaultFormula implements Seeder {
.into(SalesPriceFormulaModel)
.values([sales_formula, profit_formula])
.execute();
} catch (error) {
console.log(error, 'er');
}
} catch (error) {}
}
}

View File

@ -5,13 +5,13 @@ import { STATUS } from 'src/core/strings/constants/base.constants';
import { ItemType } from 'src/modules/item-related/item-category/constants';
import { LimitType } from 'src/modules/item-related/item/constants';
import { PaymentMethodType } from 'src/modules/transaction/payment-method/constants';
import { RefundType } from 'src/modules/transaction/refund/constants';
import { GateType } from 'src/modules/web-information/gate/constants';
@ApiTags('configuration - constant')
@Controller('v1/constant')
@Public(true)
export class ConstantController {
constructor() {}
@Get('master-data-status')
async masterDataStatus(): Promise<any> {
return [STATUS.ACTIVE, STATUS.DRAFT, STATUS.INACTIVE];
@ -31,4 +31,29 @@ export class ConstantController {
async paymentMethodType(): Promise<any> {
return Object.values(PaymentMethodType);
}
@Get('transaction-user-type')
async userType(): Promise<any> {
return ['group', 'vip'];
}
@Get('transaction-payment-type')
async transactionPaymentType(): Promise<any> {
return ['midtrans', 'bank transfer', 'qris', 'counter'];
}
@Get('transaction-type')
async transactionType(): Promise<any> {
return ['counter', 'admin', 'online'];
}
@Get('refund-type')
async refundType(): Promise<any> {
return Object.values(RefundType);
}
@Get('gate-type')
async gateType(): Promise<any> {
return Object.values(GateType);
}
}

View File

@ -1 +1 @@
export const DatabaseListen = ['transaction'];
export const DatabaseListen = ['transaction', 'vip_code'];

View File

@ -15,18 +15,64 @@ import {
SeasonPeriodDeletedHandler,
SeasonPeriodUpdatedHandler,
} from './domain/managers/season-period.handler';
import {
ItemDeletedHandler,
ItemUpdatedHandler,
} from './domain/managers/item.handler';
import {
UserDeletedHandler,
UserUpdatedHandler,
} from './domain/managers/user.handler';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import { ItemModel } from 'src/modules/item-related/item/data/models/item.model';
import { UserModel } from 'src/modules/user-related/user/data/models/user.model';
import { UserDataService } from 'src/modules/user-related/user/data/services/user-data.service';
import { ItemDataService } from 'src/modules/item-related/item/data/services/item-data.service';
import {
BookingDeletedEvent,
BookingHandler,
} from './domain/managers/booking.handler';
import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service';
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
import { TransactionTaxModel } from 'src/modules/transaction/transaction/data/models/transaction-tax.model';
import { TransactionItemModel } from 'src/modules/transaction/transaction/data/models/transaction-item.model';
import { VipCodeCreatedHandler } from './domain/managers/vip-code.handler';
@Module({
imports: [ConfigModule.forRoot(), CqrsModule],
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forFeature(
[
ItemModel,
UserModel,
TransactionModel,
TransactionTaxModel,
TransactionItemModel,
],
CONNECTION_NAME.DEFAULT,
),
CqrsModule,
],
controllers: [CouchDataController],
providers: [
BookingHandler,
BookingDeletedEvent,
PaymentMethodDeletedHandler,
PaymentMethodUpdatedHandler,
VipCodeCreatedHandler,
VipCategoryDeletedHandler,
VipCategoryUpdatedHandler,
SeasonPeriodDeletedHandler,
SeasonPeriodUpdatedHandler,
ItemUpdatedHandler,
ItemDeletedHandler,
UserDeletedHandler,
UserUpdatedHandler,
TransactionDataService,
UserDataService,
ItemDataService,
CouchService,
],
})

View File

@ -1,23 +1,32 @@
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { DatabaseListen } from '../../constants';
import { EventBus } from '@nestjs/cqrs';
import { ChangeDocEvent } from '../../domain/events/change-doc.event';
import { ConfigService } from '@nestjs/config';
import * as Nano from 'nano';
@Injectable()
export class CouchService {
constructor(private eventBus: EventBus) {}
constructor(
private eventBus: EventBus,
private configService: ConfigService,
) {}
get nanoInstance() {
const couchConfiguration = this.configService.get<string>('COUCHDB_CONFIG');
return Nano(couchConfiguration);
}
async onModuleInit() {
const nano = require('nano')(
'http://root:password@db_pos_couch_staging:5984',
);
const nano = this.nanoInstance;
for (const database of DatabaseListen) {
const db = nano.db.use(database);
db.changesReader.start({ includeDocs: true }).on('change', (change) => {
this.changeDoc(change, database);
});
console.log(`start listen database ${database}`);
Logger.log(`start listen database ${database}`, 'CouchService');
}
}
@ -33,9 +42,7 @@ export class CouchService {
public async createDoc(data, database) {
try {
const nano = require('nano')(
'http://root:password@db_pos_couch_staging:5984',
);
const nano = this.nanoInstance;
const db = nano.use(database);
return await db.insert(data);
} catch (error) {}
@ -43,9 +50,7 @@ export class CouchService {
public async deleteDoc(data, database) {
try {
const nano = require('nano')(
'http://root:password@db_pos_couch_staging:5984',
);
const nano = this.nanoInstance;
const db = nano.use(database);
const result = await db.get(data.id);
await db.destroy(data.id, result._rev);
@ -54,9 +59,7 @@ export class CouchService {
public async updateDoc(data, database) {
try {
const nano = require('nano')(
'http://root:password@db_pos_couch_staging:5984',
);
const nano = this.nanoInstance;
const db = nano.use(database);
const result = await db.get(data.id);
console.log(result, 'dsa');

View File

@ -0,0 +1,73 @@
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service';
import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event';
import { CouchService } from '../../data/services/couch.service';
import { STATUS } from 'src/core/strings/constants/base.constants';
import { TransactionPaymentType } from 'src/modules/transaction/transaction/constants';
import { mappingTransaction } from 'src/modules/transaction/transaction/domain/usecases/managers/helpers/mapping-transaction.helper';
import { TransactionDeletedEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-deleted.event';
import { TransactionUpdatedEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-updated.event';
@EventsHandler(TransactionDeletedEvent)
export class BookingDeletedEvent
implements IEventHandler<TransactionDeletedEvent>
{
constructor(private couchService: CouchService) {}
async handle(event: TransactionDeletedEvent) {
await this.couchService.deleteDoc(
{
_id: event.data.id,
...event.data.data,
},
'item',
);
}
}
@EventsHandler(TransactionChangeStatusEvent, TransactionUpdatedEvent)
export class BookingHandler
implements IEventHandler<TransactionChangeStatusEvent>
{
constructor(
private bookingService: TransactionDataService,
private couchService: CouchService,
) {}
async handle(event: TransactionChangeStatusEvent) {
const old_data = event.data.old;
const data = event.data.data;
if (data.payment_type != TransactionPaymentType.COUNTER) return;
const booking = await this.bookingService.getOneByOptions({
where: {
id: data.id,
},
relations: ['items'],
});
mappingTransaction(booking);
if (
old_data?.status != data.status &&
[STATUS.PENDING, STATUS.ACTIVE].includes(data.status)
) {
await this.couchService.createDoc(
{
_id: booking.id,
...booking,
},
'booking',
);
} else {
await this.couchService.updateDoc(
{
_id: booking.id,
...booking,
},
'booking',
);
}
}
}

View File

@ -0,0 +1,22 @@
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
import { CouchService } from '../../data/services/couch.service';
import { VipCodeCreatedEvent } from 'src/modules/transaction/vip-code/domain/entities/event/vip-code-created.event';
@EventsHandler(VipCodeCreatedEvent)
export class VipCodeCreatedHandler
implements IEventHandler<VipCodeCreatedEvent>
{
constructor(private couchService: CouchService) {}
async handle(event: VipCodeCreatedEvent) {
const data = event.data.data;
await this.couchService.createDoc(
{
_id: data.id,
...data,
},
'vip_code',
);
}
}

View File

@ -1,38 +1,44 @@
import { Body, Controller, Get, Post } from '@nestjs/common';
import { Body, Controller, Get, Injectable, Post } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Unprotected } from 'src/core/guards';
import { Public } from 'src/core/guards';
import * as Nano from 'nano';
import { CreateUserPrivilegeDto } from 'src/modules/user-related/user-privilege/infrastructure/dto/create-user-privilege.dto';
import { ConfigService } from '@nestjs/config';
@ApiTags(`couch`)
@Controller('v1/couch')
@Unprotected()
@Public()
@Injectable()
export class CouchDataController {
constructor(private configService: ConfigService) {}
get nanoInstance() {
const couchConfiguration = this.configService.get<string>('COUCHDB_CONFIG');
return Nano(couchConfiguration);
}
@Post()
async createDoc(@Body() entity: CreateUserPrivilegeDto) {
try {
let n = Nano('http://admin:secret@127.0.0.1:5984');
let db = await n.db.create(entity.name);
} catch (error) {
console.log(error, 'dsa');
}
const n = this.nanoInstance;
await n.db.create(entity.name);
} catch (error) {}
}
@Post('doc')
async createDocs(@Body() entity: CreateUserPrivilegeDto) {
try {
const nano = require('nano')('http://admin:secret@127.0.0.1:5984');
const nano = this.nanoInstance;
const people = nano.db.use('string');
console.log(await people.info());
const data = {
id: '1212',
name: 'dsadas',
};
// const data = {
// id: '1212',
// name: 'dsadas',
// };
// await people.insert(data)
people.changesReader
.start()
.start({})
.on('change', (change) => {
console.log(change);
})
@ -45,20 +51,17 @@ export class CouchDataController {
.on('error', (e) => {
console.error('error', e);
});
} catch (error) {
console.log(error, 'dsa');
}
} catch (error) {}
}
@Get()
async getDoc() {
try {
let n = Nano('http://admin:secret@127.0.0.1:5984');
const people = n.use('string');
const n = this.nanoInstance;
const people = n.db.get('user');
return people;
// return people.get();
} catch (error) {
console.log(error, 'dsa');
}
} catch (error) {}
}
}

View File

@ -0,0 +1,11 @@
import { ConfigModule } from '@nestjs/config';
import { MidnightCronManager } from './domain/managers/midnight-cron.manager';
import { Module } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
@Module({
imports: [ConfigModule.forRoot(), CqrsModule],
controllers: [MidnightCronManager],
providers: [],
})
export class CronModule {}

View File

@ -0,0 +1,3 @@
export class CronMidnightEvent {
// constructor(public readonly data: {}) {}
}

View File

@ -0,0 +1,21 @@
import { Controller } from '@nestjs/common';
import { EventBus } from '@nestjs/cqrs';
import { Cron } from '@nestjs/schedule';
import { CronMidnightEvent } from '../entities/cron-midnight.event';
@Controller()
export class MidnightCronManager {
constructor(public eventBus: EventBus) {}
@Cron(`${process.env.CRON_MIDNIGHT}`)
async scheduler(): Promise<void> {
const local_time = new Date().toLocaleDateString('en-US', {
timeZone: 'Asia/Jakarta',
});
const now = new Date(local_time).getTime();
console.log('Cron Event executed every 00:00 minutes.', now);
this.eventBus.publishAll([new CronMidnightEvent()]);
}
}

View File

@ -6,5 +6,7 @@ import { ErrorLogService } from '../../data/services/error-log.service';
export class RecordErrorLogHandler implements IEventHandler<RecordErrorLog> {
constructor(private dataservice: ErrorLogService) {}
async handle(event: RecordErrorLog) {}
async handle(event: RecordErrorLog) {
// TODO: Implement logic here
}
}

View File

@ -3,5 +3,7 @@ import { RecordLog } from '../entities/log.event';
@EventsHandler(RecordLog)
export class RecordLogHandler implements IEventHandler<RecordLog> {
async handle(event: RecordLog) {}
async handle(event: RecordLog) {
// TODO: Implement logic here
}
}

View File

@ -0,0 +1,413 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Email Ibunda</title>
<style>
/* -------------------------------------
GLOBAL RESETS
------------------------------------- */
img {
border: none;
-ms-interpolation-mode: bicubic;
max-width: 100%;
}
body {
background-color: #f6f6f6;
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
table {
border-collapse: separate;
mso-table-lspace: 0pt;
width: 100%;
}
table td {
font-family: sans-serif;
font-size: 14px;
vertical-align: top;
}
/* -------------------------------------
BODY & CONTAINER
------------------------------------- */
.body {
background-color: #f6f6f6;
width: 100%;
}
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink
down on a phone or something */
.container {
display: block;
Margin: 0 auto !important;
/* makes it centered */
max-width: 580px;
padding: 10px;
width: 580px;
}
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
box-sizing: border-box;
display: block;
Margin: 0 auto;
max-width: 580px;
padding: 10px;
}
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
.main {
background: #ffffff;
border-radius: 3px;
width: 100%;
}
.wrapper {
box-sizing: border-box;
padding: 20px;
}
.content-block {
padding-bottom: 10px;
padding-top: 10px;
}
.footer {
clear: both;
Margin-top: 10px;
text-align: center;
width: 100%;
}
.footer td,
.footer p,
.footer span,
.footer a {
color: #999999;
font-size: 12px;
text-align: center;
}
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1,
h2,
h3,
h4 {
color: #000000;
font-family: sans-serif;
font-weight: 400;
line-height: 1.4;
margin: 0;
Margin-bottom: 30px;
}
h1 {
font-size: 35px;
font-weight: 300;
text-align: center;
text-transform: capitalize;
}
p,
ul,
ol {
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
Margin-bottom: 15px;
}
p li,
ul li,
ol li {
list-style-position: inside;
margin-left: 5px;
}
a {
color: #3498db;
text-decoration: underline;
}
/* -------------------------------------
BUTTONS
------------------------------------- */
.btn {
box-sizing: border-box;
width: 100%;
}
.btn>tbody>tr>td {
padding-bottom: 15px;
}
.btn table {
width: auto;
}
.btn table td {
background-color: #ffffff;
border-radius: 5px;
text-align: center;
}
.btn a {
background-color: #ffffff;
border: solid 1px #3498db;
border-radius: 5px;
box-sizing: border-box;
color: #3498db;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 12px 25px;
text-decoration: none;
text-transform: capitalize;
}
.btn-primary table td {
background-color: #3498db;
}
.btn-primary a {
background-color: #3498db;
border-color: #3498db;
color: #ffffff;
}
/* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.last {
margin-bottom: 0;
}
.first {
margin-top: 0;
}
.align-center {
text-align: center;
}
.align-right {
text-align: right;
}
.align-left {
text-align: left;
}
.clear {
clear: both;
}
.mt0 {
margin-top: 0;
}
.mb0 {
margin-bottom: 0;
}
.preheader {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
visibility: hidden;
width: 0;
}
.powered-by a {
text-decoration: none;
}
hr {
border: 0;
border-bottom: 1px solid #f6f6f6;
Margin: 20px 0;
}
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important;
}
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important;
}
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important;
}
table[class=body] .content {
padding: 0 !important;
}
table[class=body] .container {
padding: 0 !important;
width: 100% !important;
}
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important;
}
table[class=body] .btn table {
width: 100% !important;
}
table[class=body] .btn a {
width: 100% !important;
}
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important;
}
}
/* -------------------------------------
PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */
@media all {
.ExternalClass {
width: 100%;
}
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%;
}
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important;
}
.btn-primary table td:hover {
background-color: #34495e !important;
}
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important;
}
}
ol {
padding: 0 0 0 1em;
}
ol li {
margin: 1em 0;
}
</style>
</head>
<body class="">
<table border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td>&nbsp;</td>
<td class="container">
<div class="content">
<table class="main">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<p>Halo {{customer_name}}</p>
<p>Pemesanan tiket telah berhasil dengan nomor invoice {{invoice_code}}, Silahkan lakukan pembayaran</p>
<br>
<p>
<b>PEMBAYARAN DAPAT MELALUI</b>
<ul>
{{#each payment_methods}}
<li>
<p>
<b>{{issuer_name}}</b><br>
<span>Name: <b>{{account_name}}</b></span><br>
<span>Number: <b>{{account_number}}</b></span>
</p>
</li>
{{/each}}
</ul>
</p>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td class="content-block powered-by">
Powered by Skyworld
</td>
</tr>
</table>
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER -->
</div>
</td>
<td>&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,404 @@
<!doctype html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Email Ibunda</title>
<style>
/* -------------------------------------
GLOBAL RESETS
------------------------------------- */
img {
border: none;
-ms-interpolation-mode: bicubic;
max-width: 100%;
}
body {
background-color: #f6f6f6;
font-family: sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
line-height: 1.4;
margin: 0;
padding: 0;
-ms-text-size-adjust: 100%;
-webkit-text-size-adjust: 100%;
}
table {
border-collapse: separate;
mso-table-lspace: 0pt;
width: 100%;
}
table td {
font-family: sans-serif;
font-size: 14px;
vertical-align: top;
}
/* -------------------------------------
BODY & CONTAINER
------------------------------------- */
.body {
background-color: #f6f6f6;
width: 100%;
}
/* Set a max-width, and make it display as block so it will automatically stretch to that width, but will also shrink
down on a phone or something */
.container {
display: block;
Margin: 0 auto !important;
/* makes it centered */
max-width: 580px;
padding: 10px;
width: 580px;
}
/* This should also be a block element, so that it will fill 100% of the .container */
.content {
box-sizing: border-box;
display: block;
Margin: 0 auto;
max-width: 580px;
padding: 10px;
}
/* -------------------------------------
HEADER, FOOTER, MAIN
------------------------------------- */
.main {
background: #ffffff;
border-radius: 3px;
width: 100%;
}
.wrapper {
box-sizing: border-box;
padding: 20px;
}
.content-block {
padding-bottom: 10px;
padding-top: 10px;
}
.footer {
clear: both;
Margin-top: 10px;
text-align: center;
width: 100%;
}
.footer td,
.footer p,
.footer span,
.footer a {
color: #999999;
font-size: 12px;
text-align: center;
}
/* -------------------------------------
TYPOGRAPHY
------------------------------------- */
h1,
h2,
h3,
h4 {
color: #000000;
font-family: sans-serif;
font-weight: 400;
line-height: 1.4;
margin: 0;
Margin-bottom: 30px;
}
h1 {
font-size: 35px;
font-weight: 300;
text-align: center;
text-transform: capitalize;
}
p,
ul,
ol {
font-family: sans-serif;
font-size: 14px;
font-weight: normal;
margin: 0;
Margin-bottom: 15px;
}
p li,
ul li,
ol li {
list-style-position: inside;
margin-left: 5px;
}
a {
color: #3498db;
text-decoration: underline;
}
/* -------------------------------------
BUTTONS
------------------------------------- */
.btn {
box-sizing: border-box;
width: 100%;
}
.btn>tbody>tr>td {
padding-bottom: 15px;
}
.btn table {
width: auto;
}
.btn table td {
background-color: #ffffff;
border-radius: 5px;
text-align: center;
}
.btn a {
background-color: #ffffff;
border: solid 1px #3498db;
border-radius: 5px;
box-sizing: border-box;
color: #3498db;
cursor: pointer;
display: inline-block;
font-size: 14px;
font-weight: bold;
margin: 0;
padding: 12px 25px;
text-decoration: none;
text-transform: capitalize;
}
.btn-primary table td {
background-color: #3498db;
}
.btn-primary a {
background-color: #3498db;
border-color: #3498db;
color: #ffffff;
}
/* -------------------------------------
OTHER STYLES THAT MIGHT BE USEFUL
------------------------------------- */
.last {
margin-bottom: 0;
}
.first {
margin-top: 0;
}
.align-center {
text-align: center;
}
.align-right {
text-align: right;
}
.align-left {
text-align: left;
}
.clear {
clear: both;
}
.mt0 {
margin-top: 0;
}
.mb0 {
margin-bottom: 0;
}
.preheader {
color: transparent;
display: none;
height: 0;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
visibility: hidden;
width: 0;
}
.powered-by a {
text-decoration: none;
}
hr {
border: 0;
border-bottom: 1px solid #f6f6f6;
Margin: 20px 0;
}
/* -------------------------------------
RESPONSIVE AND MOBILE FRIENDLY STYLES
------------------------------------- */
@media only screen and (max-width: 620px) {
table[class=body] h1 {
font-size: 28px !important;
margin-bottom: 10px !important;
}
table[class=body] p,
table[class=body] ul,
table[class=body] ol,
table[class=body] td,
table[class=body] span,
table[class=body] a {
font-size: 16px !important;
}
table[class=body] .wrapper,
table[class=body] .article {
padding: 10px !important;
}
table[class=body] .content {
padding: 0 !important;
}
table[class=body] .container {
padding: 0 !important;
width: 100% !important;
}
table[class=body] .main {
border-left-width: 0 !important;
border-radius: 0 !important;
border-right-width: 0 !important;
}
table[class=body] .btn table {
width: 100% !important;
}
table[class=body] .btn a {
width: 100% !important;
}
table[class=body] .img-responsive {
height: auto !important;
max-width: 100% !important;
width: auto !important;
}
}
/* -------------------------------------
PRESERVE THESE STYLES IN THE HEAD
------------------------------------- */
@media all {
.ExternalClass {
width: 100%;
}
.ExternalClass,
.ExternalClass p,
.ExternalClass span,
.ExternalClass font,
.ExternalClass td,
.ExternalClass div {
line-height: 100%;
}
.apple-link a {
color: inherit !important;
font-family: inherit !important;
font-size: inherit !important;
font-weight: inherit !important;
line-height: inherit !important;
text-decoration: none !important;
}
.btn-primary table td:hover {
background-color: #34495e !important;
}
.btn-primary a:hover {
background-color: #34495e !important;
border-color: #34495e !important;
}
}
ol {
padding: 0 0 0 1em;
}
ol li {
margin: 1em 0;
}
</style>
</head>
<body class="">
<table border="0" cellpadding="0" cellspacing="0" class="body">
<tr>
<td>&nbsp;</td>
<td class="container">
<div class="content">
<table class="main">
<!-- START MAIN CONTENT AREA -->
<tr>
<td class="wrapper">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td>
<p>Halo {{customer_name}}</p>
<p>Pemesanan tiket telah berhasil dengan nomor invoice {{invoice_code}}, Silahkan lakukan pembayaran dengan klik button dibawah ini</p>
</td>
</tr>
<tr>
<td align="center"
style="font-family: 'Lato', sans-serif; font-size:22px; color:#e5eaf5; line-height:24px; font-weight: 600;">
<a href="{{payment_midtrans_url}}">Lanjutkan Pembayaran</a>
</td>
</tr>
</table>
</td>
</tr>
<!-- END MAIN CONTENT AREA -->
</table>
<!-- START FOOTER -->
<div class="footer">
<table border="0" cellpadding="0" cellspacing="0">
<tr>
<td class="content-block powered-by">
Powered by Skyworld
</td>
</tr>
</table>
</div>
<!-- END FOOTER -->
<!-- END CENTERED WHITE CONTAINER -->
</div>
</td>
<td>&nbsp;</td>
</tr>
</table>
</body>
</html>

View File

@ -0,0 +1,61 @@
import { EventsHandler, IEventHandler } from '@nestjs/cqrs';
import { STATUS } from 'src/core/strings/constants/base.constants';
import { PaymentMethodDataService } from 'src/modules/transaction/payment-method/data/services/payment-method-data.service';
import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service';
import { TransactionChangeStatusEvent } from 'src/modules/transaction/transaction/domain/entities/event/transaction-change-status.event';
import { sendEmail } from '../helpers/send-email.helper';
import { TransactionPaymentType } from 'src/modules/transaction/transaction/constants';
@EventsHandler(TransactionChangeStatusEvent)
export class PaymentTransactionHandler
implements IEventHandler<TransactionChangeStatusEvent>
{
constructor(
private dataService: TransactionDataService,
private paymentService: PaymentMethodDataService,
) {}
async handle(event: TransactionChangeStatusEvent) {
const data_id = event.data.id;
const old_data = event.data.old;
const current_data = event.data.data;
let payments = [];
if (
old_data.status == STATUS.DRAFT &&
current_data.status == STATUS.PENDING &&
current_data.payment_type != TransactionPaymentType.COUNTER &&
!!current_data.customer_email
) {
if (current_data.payment_type != TransactionPaymentType.MIDTRANS) {
payments = await this.paymentService.getManyByOptions({
where: {
status: STATUS.ACTIVE,
},
});
}
const transaction = await this.dataService.getOneByOptions({
where: {
id: data_id,
},
relations: ['items'],
});
try {
sendEmail(
[
{
...transaction,
email: transaction.customer_email,
payment_methods: payments,
},
],
'Payment Confirmation',
);
} catch (error) {
console.log(error);
}
}
}
}

View File

@ -0,0 +1,50 @@
import * as nodemailer from 'nodemailer';
import * as handlebars from 'handlebars';
import * as path from 'path';
import * as fs from 'fs';
import { TransactionPaymentType } from 'src/modules/transaction/transaction/constants';
export async function sendEmail(receivers, subject) {
const smtpTransport = nodemailer.createTransport({
host: process.env.EMAIL_HOST,
port: process.env.EMAIL_POST,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_TOKEN,
},
});
let templateName = 'payment-confirmation-bank';
for (const receiver of receivers) {
if (receiver.payment_type == TransactionPaymentType.MIDTRANS)
templateName = 'payment-confirmation-midtrans';
let templatePath = path.resolve(
__dirname,
`../email-template/${templateName}.html`,
);
templatePath = templatePath.replace(/dist/g, 'src');
const templateSource = fs.readFileSync(templatePath, 'utf8');
const template = handlebars.compile(templateSource);
const htmlToSend = template(receiver);
const emailContext = {
from: 'no-reply@eigen.co.id',
to: receiver.email,
subject: subject,
html: htmlToSend,
attachDataUrls: true,
};
await new Promise((f) => setTimeout(f, 2000));
smtpTransport.sendMail(emailContext, function (err, data) {
if (err) {
console.log(`Error occurs on send to ${receiver.email}`);
} else {
console.log(`Email sent to ${receiver.email}`);
}
});
}
}

View File

@ -0,0 +1,28 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { CqrsModule } from '@nestjs/cqrs';
import { PaymentMethodModel } from 'src/modules/transaction/payment-method/data/models/payment-method.model';
import { PaymentMethodDataService } from 'src/modules/transaction/payment-method/data/services/payment-method-data.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { CONNECTION_NAME } from 'src/core/strings/constants/base.constants';
import { TransactionModel } from 'src/modules/transaction/transaction/data/models/transaction.model';
import { TransactionDataService } from 'src/modules/transaction/transaction/data/services/transaction-data.service';
import { PaymentTransactionHandler } from './domain/handlers/payment-transaction.handler';
@Module({
imports: [
ConfigModule.forRoot(),
TypeOrmModule.forFeature(
[PaymentMethodModel, TransactionModel],
CONNECTION_NAME.DEFAULT,
),
CqrsModule,
],
controllers: [],
providers: [
PaymentTransactionHandler,
PaymentMethodDataService,
TransactionDataService,
],
})
export class MailModule {}

View File

@ -0,0 +1,26 @@
import { Injectable } from '@nestjs/common';
import { EventBus } from '@nestjs/cqrs';
import { mappingMidtransTransaction } from '../../domain/usecases/helpers/mapping-transaction.helper';
const midtransClient = require('midtrans-client');
@Injectable()
export class MidtransService {
constructor(private eventBus: EventBus) {}
get midtransInstance() {
return new midtransClient.Snap({
isProduction: false,
serverKey: process.env.MIDTRANS_SERVER_KEY,
clientKey: process.env.MIDTRANS_CLIENT_KEY,
});
}
async getStatus(orderId: string): Promise<any> {
return await this.midtransInstance.transaction.status(orderId);
}
async create(body): Promise<any> {
const data = mappingMidtransTransaction(body);
return await this.midtransInstance.createTransaction(data);
}
}

View File

@ -0,0 +1,8 @@
export class MidtransCallbackEvent {
constructor(public readonly data: IEventMidtrans) {}
}
export interface IEventMidtrans {
id: string;
data: any;
}

View File

@ -0,0 +1,31 @@
export function mappingMidtransTransaction(transaction) {
const item_details = transaction.items?.map((item) => {
return {
quantity: Number(item.qty),
price: Number(item.total_price),
name: item.item_name,
category: item.item_category_name,
};
});
if (transaction.payment_discount_total) {
item_details.push({
quantity: 1,
price: -Number(transaction.payment_discount_total),
name: 'discount',
});
}
return {
transaction_details: {
order_id: transaction.id,
gross_amount: Number(transaction.payment_total),
},
item_details: item_details,
customer_details: {
first_name: transaction.customer_name,
email: transaction.customer_email,
phone: transaction.customer_phone,
},
};
}

View File

@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class MidtransDto {
@ApiProperty({
type: String,
example: '123',
})
order_id: string;
}

View File

@ -0,0 +1,36 @@
import { Body, Controller, Get, Injectable, Param, Post } from '@nestjs/common';
import { ApiTags } from '@nestjs/swagger';
import { Public } from 'src/core/guards';
import { MidtransService } from '../data/services/midtrans.service';
import { EventBus } from '@nestjs/cqrs';
import { MidtransCallbackEvent } from '../domain/entities/midtrans-callback.event';
import { MidtransDto } from './dto/midtrans.dto';
@ApiTags(`midtrans`)
@Controller('v1/midtrans')
@Public()
@Injectable()
export class MidtransController {
constructor(
private dataService: MidtransService,
private eventBus: EventBus,
) {}
@Get(':id/status')
async getStatus(@Param('id') id: string) {
return await this.dataService.getStatus(id);
}
@Post('callback')
async callback(@Body() callback: MidtransDto) {
const data = await this.dataService.getStatus(callback?.order_id);
this.eventBus.publishAll([
new MidtransCallbackEvent({
id: data.order_id,
data: data,
}),
]);
console.log(`midtrans callback for order ${data.order_id}`);
return 'success listen callback';
}
}

View File

@ -0,0 +1,14 @@
import { ConfigModule } from '@nestjs/config';
import { CqrsModule } from '@nestjs/cqrs';
import { MidtransController } from './infrastructure/midtrans.controller';
import { MidtransService } from './data/services/midtrans.service';
import { Global, Module } from '@nestjs/common';
@Global()
@Module({
imports: [ConfigModule.forRoot(), CqrsModule],
controllers: [MidtransController],
providers: [MidtransService],
exports: [MidtransService],
})
export class MidtransModule {}

View File

@ -0,0 +1,5 @@
export interface UploadEntity {
module: string;
sub_module: string;
file: any;
}

View File

@ -0,0 +1,22 @@
import { ApiProperty } from '@nestjs/swagger';
import { UploadEntity } from '../../domain/entities/upload.entity';
import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
export class UploadDto implements UploadEntity {
@ApiProperty({
type: 'string',
required: true,
default: TABLE_NAME.ITEM,
})
module: string;
@ApiProperty({
type: 'string',
required: false,
default: TABLE_NAME.ITEM,
})
sub_module: string;
@ApiProperty({ type: 'string', format: 'binary', required: true })
file: any;
}

View File

@ -0,0 +1,34 @@
import {
Controller,
Post,
UseInterceptors,
Body,
UploadedFile,
} from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
import { ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
import { FileInterceptor } from '@nestjs/platform-express';
import { UploadDto } from './dto/upload.dto';
import { Public } from 'src/core/guards';
import { StoreFileConfig } from 'src/core/helpers/path/upload-store-path.helper';
@ApiTags('uploads')
@Controller('uploads')
@Public(true)
export class UploadController {
constructor(private moduleRef: ModuleRef) {}
@Post()
@ApiConsumes('multipart/form-data')
@ApiBody({ type: UploadDto })
@UseInterceptors(FileInterceptor('file', StoreFileConfig))
async storeFile(
@UploadedFile() file: Express.Multer.File,
@Body() body: UploadDto,
): Promise<any> {
return {
path: file.path,
full_path: `${process.env.ASSETS}${file.path}`,
};
}
}

View File

@ -0,0 +1,16 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { MulterModule } from '@nestjs/platform-express';
import { UploadController } from './infrastructure/upload.controller';
@Module({
imports: [
ConfigModule.forRoot(),
MulterModule.register({
dest: './uploads',
}),
],
controllers: [UploadController],
providers: [],
})
export class UploadModule {}

View File

@ -49,7 +49,7 @@ export class ItemCategoryDataOrchestrator extends BaseDataTransactionOrchestrato
return this.updateManager.getResult();
}
async delete(dataId): Promise<String> {
async delete(dataId): Promise<string> {
this.deleteManager.setData(dataId);
this.deleteManager.setService(this.serviceData, TABLE_NAME.ITEM_CATEGORY);
await this.deleteManager.execute();
@ -66,7 +66,7 @@ export class ItemCategoryDataOrchestrator extends BaseDataTransactionOrchestrato
return this.batchDeleteManager.getResult();
}
async active(dataId): Promise<String> {
async active(dataId): Promise<string> {
this.activeManager.setData(dataId, STATUS.ACTIVE);
this.activeManager.setService(this.serviceData, TABLE_NAME.ITEM_CATEGORY);
await this.activeManager.execute();
@ -83,7 +83,7 @@ export class ItemCategoryDataOrchestrator extends BaseDataTransactionOrchestrato
return this.batchActiveManager.getResult();
}
async confirm(dataId): Promise<String> {
async confirm(dataId): Promise<string> {
this.confirmManager.setData(dataId, STATUS.ACTIVE);
this.confirmManager.setService(this.serviceData, TABLE_NAME.ITEM_CATEGORY);
await this.confirmManager.execute();
@ -100,7 +100,7 @@ export class ItemCategoryDataOrchestrator extends BaseDataTransactionOrchestrato
return this.batchConfirmManager.getResult();
}
async inactive(dataId): Promise<String> {
async inactive(dataId): Promise<string> {
this.inactiveManager.setData(dataId, STATUS.INACTIVE);
this.inactiveManager.setService(this.serviceData, TABLE_NAME.ITEM_CATEGORY);
await this.inactiveManager.execute();

View File

@ -22,7 +22,9 @@ export class CreateItemCategoryManager extends BaseCreateManager<ItemCategoryEnt
return;
}
async generateConfig(): Promise<void> {}
async generateConfig(): Promise<void> {
// TODO: Implement logic here
}
get validateRelations(): validateRelations[] {
return [];

View File

@ -34,7 +34,7 @@ export class ItemCategoryDataController {
}
@Patch(':id/active')
async active(@Param('id') dataId: string): Promise<String> {
async active(@Param('id') dataId: string): Promise<string> {
return await this.orchestrator.active(dataId);
}
@ -44,7 +44,7 @@ export class ItemCategoryDataController {
}
@Patch(':id/confirm')
async confirm(@Param('id') dataId: string): Promise<String> {
async confirm(@Param('id') dataId: string): Promise<string> {
return await this.orchestrator.confirm(dataId);
}
@ -54,7 +54,7 @@ export class ItemCategoryDataController {
}
@Patch(':id/inactive')
async inactive(@Param('id') dataId: string): Promise<String> {
async inactive(@Param('id') dataId: string): Promise<string> {
return await this.orchestrator.inactive(dataId);
}
@ -72,7 +72,7 @@ export class ItemCategoryDataController {
}
@Delete(':id')
async delete(@Param('id') dataId: string): Promise<String> {
async delete(@Param('id') dataId: string): Promise<string> {
return await this.orchestrator.delete(dataId);
}
}

View File

@ -36,7 +36,7 @@ export class ItemRateDataOrchestrator extends BaseDataOrchestrator<ItemRateEntit
return this.updateManager.getResult();
}
async delete(dataId): Promise<String> {
async delete(dataId): Promise<string> {
this.deleteManager.setData(dataId);
this.deleteManager.setService(this.serviceData, TABLE_NAME.ITEM_RATE);
await this.deleteManager.execute();

View File

@ -6,6 +6,7 @@ import {
RelationParam,
} from 'src/core/modules/domain/entities/base-filter.entity';
import { ItemEntity } from 'src/modules/item-related/item/domain/entities/item.entity';
import { STATUS } from 'src/core/strings/constants/base.constants';
@Injectable()
export class IndexItemRateManager extends BaseIndexManager<ItemEntity> {
@ -19,7 +20,7 @@ export class IndexItemRateManager extends BaseIndexManager<ItemEntity> {
async afterProcess(): Promise<void> {
this.result.data?.map((item) => {
let prices = [];
const prices = [];
for (
let d = new Date(this.filterParam.start_date);
d <= new Date(this.filterParam.end_date);
@ -27,6 +28,7 @@ export class IndexItemRateManager extends BaseIndexManager<ItemEntity> {
) {
const rate = item['item_rates']?.find(
(rate) =>
rate.season_period?.status == STATUS.ACTIVE &&
d >= new Date(rate.season_period.start_date) &&
d <= new Date(rate.season_period.end_date),
);
@ -70,6 +72,7 @@ export class IndexItemRateManager extends BaseIndexManager<ItemEntity> {
get selects(): string[] {
return [
`${this.tableName}.id`,
`${this.tableName}.status`,
`${this.tableName}.created_at`,
`${this.tableName}.name`,
`${this.tableName}.base_price`,
@ -84,6 +87,7 @@ export class IndexItemRateManager extends BaseIndexManager<ItemEntity> {
'item_rates.price',
'season_period.id',
'season_period.status',
'season_period.holiday_name',
'season_period.start_date',
'season_period.end_date',
@ -121,10 +125,11 @@ export class IndexItemRateManager extends BaseIndexManager<ItemEntity> {
queryBuilder.andWhere(`${this.tableName}.tenant_id In (:...tenantIds)`, {
tenantIds: this.filterParam.tenant_ids,
});
} else if (!this.filterParam.all_item) {
queryBuilder.andWhere(`${this.tableName}.tenant_id Is Null`);
}
queryBuilder.andWhere(`${this.tableName}.status In (:...statuses)`, {
statuses: [STATUS.ACTIVE],
});
return queryBuilder;
}
}

View File

@ -38,7 +38,7 @@ export class ItemRateDataController {
}
// @Delete(':id')
// async delete(@Param('id') dataId: string): Promise<String> {
// async delete(@Param('id') dataId: string): Promise<string> {
// return await this.orchestrator.delete(dataId);
// }
}

View File

@ -15,6 +15,7 @@ import { LimitType } from '../../constants';
import { ItemCategoryModel } from 'src/modules/item-related/item-category/data/models/item-category.model';
import { UserModel } from 'src/modules/user-related/user/data/models/user.model';
import { ItemRateModel } from 'src/modules/item-related/item-rate/data/models/item-rate.model';
import { GateModel } from 'src/modules/web-information/gate/data/models/gate.model';
@Entity(TABLE_NAME.ITEM)
export class ItemModel
@ -24,8 +25,8 @@ export class ItemModel
@Column('varchar', { name: 'name' })
name: string;
@Column('varchar', { name: 'image', nullable: true })
image: string;
@Column('varchar', { name: 'image_url', nullable: true })
image_url: string;
@Column('enum', {
name: 'item_type',
@ -108,4 +109,11 @@ export class ItemModel
onUpdate: 'CASCADE',
})
item_rates: ItemRateModel[];
// relasi ke gate
@OneToMany(() => GateModel, (model) => model.item, {
onDelete: 'CASCADE',
onUpdate: 'CASCADE',
})
gates: GateModel[];
}

View File

@ -6,4 +6,5 @@ export interface FilterItemEntity extends BaseFilterEntity {
limit_types: string[];
tenant_ids: string[];
all_item: boolean;
season_period_ids: string[];
}

View File

@ -5,7 +5,7 @@ import { LimitType } from '../../constants';
export interface ItemEntity extends BaseStatusEntity {
name: string;
item_type: ItemType;
image: string;
image_url: string;
hpp: number;
sales_margin: number;

View File

@ -15,6 +15,8 @@ import { BatchInactiveItemManager } from './managers/batch-inactive-item.manager
import { BatchActiveItemManager } from './managers/batch-active-item.manager';
import { BatchDeleteItemManager } from './managers/batch-delete-item.manager';
import { TABLE_NAME } from 'src/core/strings/constants/table.constants';
import { UpdateItemRatePriceManager } from './managers/update-item-rate-price.manager';
import { ItemRateReadService } from 'src/modules/item-related/item-rate/data/services/item-rate-read.service';
@Injectable()
export class ItemDataOrchestrator extends BaseDataTransactionOrchestrator<ItemEntity> {
@ -28,8 +30,10 @@ export class ItemDataOrchestrator extends BaseDataTransactionOrchestrator<ItemEn
private batchDeleteManager: BatchDeleteItemManager,
private batchActiveManager: BatchActiveItemManager,
private batchConfirmManager: BatchConfirmItemManager,
private updatePriceManager: UpdateItemRatePriceManager,
private batchInactiveManager: BatchInactiveItemManager,
private serviceData: ItemDataService,
private serviceRateData: ItemRateReadService,
) {
super();
}
@ -60,7 +64,14 @@ export class ItemDataOrchestrator extends BaseDataTransactionOrchestrator<ItemEn
return this.updateManager.getResult();
}
async delete(dataId, tenantId?: string): Promise<String> {
async updatePrice(data): Promise<any[]> {
this.updatePriceManager.setData(data);
this.updatePriceManager.setService(this.serviceRateData, TABLE_NAME.ITEM);
await this.updatePriceManager.execute();
return this.updatePriceManager.getResult();
}
async delete(dataId, tenantId?: string): Promise<string> {
this.deleteManager.setData(dataId);
this.deleteManager.setService(this.serviceData, TABLE_NAME.ITEM);
await this.deleteManager.execute();
@ -77,7 +88,7 @@ export class ItemDataOrchestrator extends BaseDataTransactionOrchestrator<ItemEn
return this.batchDeleteManager.getResult();
}
async active(dataId, tenantId?: string): Promise<String> {
async active(dataId, tenantId?: string): Promise<string> {
this.activeManager.setData(dataId, STATUS.ACTIVE);
this.activeManager.setService(this.serviceData, TABLE_NAME.ITEM);
await this.activeManager.execute();
@ -94,7 +105,7 @@ export class ItemDataOrchestrator extends BaseDataTransactionOrchestrator<ItemEn
return this.batchActiveManager.getResult();
}
async confirm(dataId, tenantId?: string): Promise<String> {
async confirm(dataId, tenantId?: string): Promise<string> {
this.confirmManager.setData(dataId, STATUS.ACTIVE);
this.confirmManager.setService(this.serviceData, TABLE_NAME.ITEM);
await this.confirmManager.execute();
@ -111,7 +122,7 @@ export class ItemDataOrchestrator extends BaseDataTransactionOrchestrator<ItemEn
return this.batchConfirmManager.getResult();
}
async inactive(dataId, tenantId?: string): Promise<String> {
async inactive(dataId, tenantId?: string): Promise<string> {
this.inactiveManager.setData(dataId, STATUS.INACTIVE);
this.inactiveManager.setService(this.serviceData, TABLE_NAME.ITEM);
await this.inactiveManager.execute();

View File

@ -33,6 +33,7 @@ export class DetailItemManager extends BaseDetailManager<ItemEntity> {
get selects(): string[] {
return [
`${this.tableName}.id`,
`${this.tableName}.image_url`,
`${this.tableName}.created_at`,
`${this.tableName}.status`,
`${this.tableName}.item_type`,

View File

@ -10,7 +10,7 @@ import { ItemRateEntity } from 'src/modules/item-related/item-rate/domain/entiti
@Injectable()
export class IndexItemRatesManager extends BaseIndexManager<ItemRateEntity> {
async prepareData(): Promise<void> {
this.filterParam.order_by = `${this.tableName}.id`;
this.filterParam.order_by = `season_period.id`;
return;
}
@ -38,9 +38,11 @@ export class IndexItemRatesManager extends BaseIndexManager<ItemRateEntity> {
get selects(): string[] {
return [
`${this.tableName}.id`,
`${this.tableName}.item_id`,
`${this.tableName}.price`,
`season_period.id`,
`season_period.priority`,
`season_period.created_at`,
`season_period.creator_name`,
`season_period.editor_name`,
@ -68,6 +70,25 @@ export class IndexItemRatesManager extends BaseIndexManager<ItemRateEntity> {
itemIds: this.filterParam.item_ids,
});
}
if (this.filterParam.season_period_ids) {
queryBuilder.andWhere(`season_period.id In (:...seasonIdss)`, {
seasonIdss: this.filterParam.season_period_ids,
});
}
if (this.filterParam.start_date) {
queryBuilder.andWhere(`season_period.start_date <= :inputStartDate`, {
inputStartDate: this.filterParam.end_date,
});
queryBuilder.andWhere(`season_period.end_date >= :inputEndDate`, {
inputEndDate: this.filterParam.start_date,
});
}
queryBuilder.addOrderBy('season_period.priority', 'ASC');
return queryBuilder;
}
}

View File

@ -0,0 +1,72 @@
import { Injectable } from '@nestjs/common';
import { BaseCustomManager } from 'src/core/modules/domain/usecase/managers/base-custom.manager';
import { ItemEntity } from '../../entities/item.entity';
import { EventTopics } from 'src/core/strings/constants/interface.constants';
import { ItemModel } from '../../../data/models/item.model';
import { In, LessThanOrEqual, MoreThanOrEqual } from 'typeorm';
@Injectable()
export class UpdateItemRatePriceManager extends BaseCustomManager<ItemEntity> {
protected rates = [];
get entityTarget(): any {
return ItemModel;
}
get eventTopics(): EventTopics[] {
return [];
}
async validateProcess(): Promise<void> {
return;
}
async beforeProcess(): Promise<void> {
let query;
const item_ids = this.data.items.map((item) => {
return item.item.id;
});
if (this.data.season_period_id) {
query = {
item_id: In(item_ids),
season_period: {
id: this.data.season_period_id,
},
};
} else {
query = {
item_id: In(item_ids),
season_period: {
start_date: MoreThanOrEqual(this.data.booking_date),
end_date: LessThanOrEqual(this.data.booking_date),
},
};
}
this.rates = await this.dataService.getManyByOptions({
where: query,
});
return;
}
async process(): Promise<void> {
this.data.items.map((item) => {
const current_price = this.rates.find(
(rate) => rate.item_id == item.item.id,
);
Object.assign(item, {
total_price: current_price?.price ?? item.item.base_price,
});
});
return;
}
async afterProcess(): Promise<void> {
return;
}
async getResult() {
return this.data.items;
}
}

View File

@ -10,6 +10,12 @@ export class FilterItemDto extends BaseFilterDto implements FilterItemEntity {
})
item_categories: string[];
@ApiProperty({ type: ['string'], required: false })
@Transform((body) => {
return Array.isArray(body.value) ? body.value : [body.value];
})
season_period_ids: string[];
@ApiProperty({ type: ['string'], required: false })
@Transform((body) => {
return Array.isArray(body.value) ? body.value : [body.value];

View File

@ -29,7 +29,7 @@ export class ItemDto extends BaseStatusDto implements ItemEntity {
})
@IsString()
@ValidateIf((body) => body.image)
image: string;
image_url: string;
@ApiProperty({
type: 'string',

View File

@ -0,0 +1,43 @@
import { ApiProperty } from '@nestjs/swagger';
export class UpdateItemPriceDto {
@ApiProperty({
type: [Object],
required: true,
example: [
{
item: {
id: 'bee5c493-fb35-4ceb-b7a1-7bc3edb3c63b',
name: 'TEnant 2 wahana air',
item_type: 'wahana',
base_price: '100000',
hpp: '0',
tenant: {
id: 'e19a4637-d4db-48cc-89ce-501913d07cdd',
name: 'e19a4637-d4db-48cc-89ce-501913d07cdd',
share_margin: null,
},
item_category: {
id: '88633772-ec34-4645-bc04-6cfdce6af0cf',
name: 'Wahana Air',
},
},
qty: 1,
total_price: '100000',
},
],
})
items: Object[];
@ApiProperty({
type: String,
example: 'uuid',
})
season_period_id: string;
@ApiProperty({
type: Date,
example: '2024-08-17',
})
booking_date: Date;
}

View File

@ -15,6 +15,7 @@ import { ItemEntity } from '../domain/entities/item.entity';
import { BatchResult } from 'src/core/response/domain/ok-response.interface';
import { BatchIdsDto } from 'src/core/modules/infrastructure/dto/base-batch.dto';
import { Public } from 'src/core/guards';
import { UpdateItemPriceDto } from './dto/update-item-price.dto';
@ApiTags(`${MODULE_NAME.ITEM.split('-').join(' ')} - data`)
@Controller(`v1/${MODULE_NAME.ITEM}`)
@ -28,13 +29,18 @@ export class ItemDataController {
return await this.orchestrator.create(data);
}
@Post('update-price')
async updatePrice(@Body() body: UpdateItemPriceDto): Promise<any> {
return await this.orchestrator.updatePrice(body);
}
@Put('/batch-delete')
async batchDeleted(@Body() body: BatchIdsDto): Promise<BatchResult> {
return await this.orchestrator.batchDelete(body.ids);
}
@Patch(':id/active')
async active(@Param('id') dataId: string): Promise<String> {
async active(@Param('id') dataId: string): Promise<string> {
return await this.orchestrator.active(dataId);
}
@ -44,7 +50,7 @@ export class ItemDataController {
}
@Patch(':id/confirm')
async confirm(@Param('id') dataId: string): Promise<String> {
async confirm(@Param('id') dataId: string): Promise<string> {
return await this.orchestrator.confirm(dataId);
}
@ -54,7 +60,7 @@ export class ItemDataController {
}
@Patch(':id/inactive')
async inactive(@Param('id') dataId: string): Promise<String> {
async inactive(@Param('id') dataId: string): Promise<string> {
return await this.orchestrator.inactive(dataId);
}
@ -72,7 +78,7 @@ export class ItemDataController {
}
@Delete(':id')
async delete(@Param('id') dataId: string): Promise<String> {
async delete(@Param('id') dataId: string): Promise<string> {
return await this.orchestrator.delete(dataId);
}
}

Some files were not shown because too many files have changed in this diff Show More