add database migration for order

This commit is contained in:
ItsMalma
2026-01-08 20:54:31 +07:00
parent 4124d9bf19
commit d95811ad83
4 changed files with 283 additions and 166 deletions

View File

@@ -2,6 +2,8 @@ import { PaymentError } from "@/common/errors/payment.error";
import { AbstractPaymentService } from "@/common/services/payment-service/abstract.payment-service"; import { AbstractPaymentService } from "@/common/services/payment-service/abstract.payment-service";
import { midtransConfig } from "@/configs/midtrans.config"; import { midtransConfig } from "@/configs/midtrans.config";
import type { Order } from "@/database/entities/order.entity"; import type { Order } from "@/database/entities/order.entity";
import { Kit } from "@/database/enums/kit.enum";
import { OrderType } from "@/database/enums/order-type.enum";
type CreateTransactionResponseSuccess = { type CreateTransactionResponseSuccess = {
token: string; token: string;
@@ -11,6 +13,12 @@ type CreateTransactionResponseFailed = {
error_messages: string[]; error_messages: string[];
}; };
const BOOKING_SEAT_PRICE = 1_000_000;
const MINIMAL_KIT_PRICE = 350_000;
const WITHOUT_SUITCASE_KIT_PRICE = 350_000;
const FULL_KIT_PRICE = 350_000;
const VACCINE_PRICE = 500_000;
export class MidtransPaymentService extends AbstractPaymentService { export class MidtransPaymentService extends AbstractPaymentService {
private readonly _basicAuth: string; private readonly _basicAuth: string;
@@ -20,17 +28,48 @@ export class MidtransPaymentService extends AbstractPaymentService {
this._basicAuth = `Basic ${Buffer.from(`${midtransConfig.serverKey}:`).toBase64()}`; this._basicAuth = `Basic ${Buffer.from(`${midtransConfig.serverKey}:`).toBase64()}`;
} }
private calculateOrderPrice(order: Order): number {
return (
order.package.quadPrice * order.quad +
order.package.triplePrice * order.triple +
order.package.doublePrice * order.double +
(order.package.infantPrice ?? 0) * order.infant
);
}
public async createPaymentUrl(order: Order): Promise<string> { public async createPaymentUrl(order: Order): Promise<string> {
const price = this.calculateOrderPrice(order); const quantity = order.quad + order.triple + order.double + order.infant;
let price = 0;
if (order.type === OrderType.bookingSeat) {
price += BOOKING_SEAT_PRICE * quantity;
} else if (order.type === OrderType.downPayment) {
price +=
Math.ceil(
((order.quadDownPaymentPercentage / 100) *
(order.package.quadPrice - order.package.quadDiscount)) /
100_000,
) *
100_000 +
Math.ceil(
((order.tripleDownPaymentPercentage / 100) *
(order.package.triplePrice - order.package.tripleDiscount)) /
100_000,
) *
100_000 +
Math.ceil(
((order.doubleDownPaymentPercentage / 100) *
(order.package.doublePrice - order.package.doubleDiscount)) /
100_000,
) *
100_000 +
Math.ceil(
((order.infantDownPaymentPercentage / 100) *
((order.package.infantPrice ?? 0) -
(order.package.infantDiscount ?? 0))) /
100_000,
) *
100_000;
}
if (order.kit === Kit.minimal) {
price += MINIMAL_KIT_PRICE;
} else if (order.kit === Kit.withoutSuitcase) {
price += WITHOUT_SUITCASE_KIT_PRICE;
} else if (order.kit === Kit.full) {
price += FULL_KIT_PRICE;
}
price += VACCINE_PRICE * quantity;
const response = await fetch(`${midtransConfig.baseUrl}/transactions`, { const response = await fetch(`${midtransConfig.baseUrl}/transactions`, {
method: "POST", method: "POST",
@@ -42,7 +81,7 @@ export class MidtransPaymentService extends AbstractPaymentService {
body: JSON.stringify({ body: JSON.stringify({
transaction_details: { transaction_details: {
order_id: order.id, order_id: order.id,
gross_amount: this.calculateOrderPrice(order), gross_amount: price,
}, },
item_details: [ item_details: [
{ {

View File

@@ -1,10 +1,7 @@
import { Order } from "@/database/entities/order.entity";
import { Verification } from "@/database/entities/verification.entity"; import { Verification } from "@/database/entities/verification.entity";
import { import {
Collection,
Entity, Entity,
ManyToOne, ManyToOne,
OneToMany,
PrimaryKey, PrimaryKey,
Property, Property,
type Rel, type Rel,
@@ -45,9 +42,4 @@ export class Partner {
onUpdate: () => new Date(), onUpdate: () => new Date(),
}) })
updatedAt!: Date; updatedAt!: Date;
// Collections
@OneToMany(() => Order, (order) => order.partner)
orders = new Collection<Order>(this);
} }

View File

@@ -3149,16 +3149,6 @@
"length": 30, "length": 30,
"mappedType": "string" "mappedType": "string"
}, },
"code": {
"name": "code",
"type": "varchar(6)",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"mappedType": "string"
},
"package_id": { "package_id": {
"name": "package_id", "name": "package_id",
"type": "varchar(30)", "type": "varchar(30)",
@@ -3169,89 +3159,113 @@
"length": 30, "length": 30,
"mappedType": "string" "mappedType": "string"
}, },
"created_at": { "quad": {
"name": "created_at", "name": "quad",
"type": "timestamptz", "type": "int",
"unsigned": false, "unsigned": true,
"autoincrement": false, "autoincrement": false,
"primary": false, "primary": false,
"nullable": false, "nullable": false,
"length": 6, "mappedType": "integer"
"mappedType": "datetime"
}, },
"updated_at": { "triple": {
"name": "updated_at", "name": "triple",
"type": "timestamptz", "type": "int",
"unsigned": false, "unsigned": true,
"autoincrement": false, "autoincrement": false,
"primary": false, "primary": false,
"nullable": false, "nullable": false,
"length": 6, "mappedType": "integer"
"mappedType": "datetime"
}
},
"name": "package_consult_session",
"schema": "public",
"indexes": [
{
"columnNames": [
"code"
],
"composite": false,
"keyName": "package_consult_session_code_unique",
"constraint": true,
"primary": false,
"unique": true
}, },
{ "double": {
"keyName": "package_consult_session_pkey", "name": "double",
"columnNames": [ "type": "int",
"id" "unsigned": true,
],
"composite": false,
"constraint": true,
"primary": true,
"unique": true
}
],
"checks": [],
"foreignKeys": {
"package_consult_session_package_id_foreign": {
"constraintName": "package_consult_session_package_id_foreign",
"columnNames": [
"package_id"
],
"localTableName": "public.package_consult_session",
"referencedColumnNames": [
"id"
],
"referencedTableName": "public.package",
"updateRule": "cascade"
}
},
"nativeEnums": {}
},
{
"columns": {
"id": {
"name": "id",
"type": "varchar(30)",
"unsigned": false,
"autoincrement": false, "autoincrement": false,
"primary": false, "primary": false,
"nullable": false, "nullable": false,
"length": 30, "mappedType": "integer"
"mappedType": "string"
}, },
"package_id": { "infant": {
"name": "package_id", "name": "infant",
"type": "varchar(30)", "type": "int",
"unsigned": true,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "integer"
},
"type": {
"name": "type",
"type": "text",
"unsigned": false, "unsigned": false,
"autoincrement": false, "autoincrement": false,
"primary": false, "primary": false,
"nullable": false, "nullable": false,
"length": 30, "enumItems": [
"mappedType": "string" "booking_seat",
"down_payment"
],
"mappedType": "enum"
},
"quad_down_payment_percentage": {
"name": "quad_down_payment_percentage",
"type": "int",
"unsigned": true,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "integer"
},
"triple_down_payment_percentage": {
"name": "triple_down_payment_percentage",
"type": "int",
"unsigned": true,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "integer"
},
"double_down_payment_percentage": {
"name": "double_down_payment_percentage",
"type": "int",
"unsigned": true,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "integer"
},
"infant_down_payment_percentage": {
"name": "infant_down_payment_percentage",
"type": "int",
"unsigned": true,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "integer"
},
"kit": {
"name": "kit",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"enumItems": [
"minimal",
"without_suitcase",
"full"
],
"mappedType": "enum"
},
"vaccine": {
"name": "vaccine",
"type": "int",
"unsigned": true,
"autoincrement": false,
"primary": false,
"nullable": false,
"mappedType": "integer"
}, },
"name": { "name": {
"name": "name", "name": "name",
@@ -3273,26 +3287,6 @@
"length": 20, "length": 20,
"mappedType": "string" "mappedType": "string"
}, },
"verification_id": {
"name": "verification_id",
"type": "varchar(30)",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"length": 30,
"mappedType": "string"
},
"partner_id": {
"name": "partner_id",
"type": "varchar(30)",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": true,
"length": 30,
"mappedType": "string"
},
"expired_at": { "expired_at": {
"name": "expired_at", "name": "expired_at",
"type": "timestamptz", "type": "timestamptz",
@@ -3371,32 +3365,6 @@
], ],
"referencedTableName": "public.package_detail", "referencedTableName": "public.package_detail",
"updateRule": "cascade" "updateRule": "cascade"
},
"order_verification_id_foreign": {
"constraintName": "order_verification_id_foreign",
"columnNames": [
"verification_id"
],
"localTableName": "public.order",
"referencedColumnNames": [
"id"
],
"referencedTableName": "public.verification",
"deleteRule": "set null",
"updateRule": "cascade"
},
"order_partner_id_foreign": {
"constraintName": "order_partner_id_foreign",
"columnNames": [
"partner_id"
],
"localTableName": "public.order",
"referencedColumnNames": [
"id"
],
"referencedTableName": "public.partner",
"deleteRule": "set null",
"updateRule": "cascade"
} }
}, },
"nativeEnums": {} "nativeEnums": {}
@@ -3413,8 +3381,18 @@
"length": 30, "length": 30,
"mappedType": "string" "mappedType": "string"
}, },
"order_id": { "code": {
"name": "order_id", "name": "code",
"type": "varchar(6)",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"length": 6,
"mappedType": "string"
},
"package_id": {
"name": "package_id",
"type": "varchar(30)", "type": "varchar(30)",
"unsigned": false, "unsigned": false,
"autoincrement": false, "autoincrement": false,
@@ -3423,21 +3401,6 @@
"length": 30, "length": 30,
"mappedType": "string" "mappedType": "string"
}, },
"room_type": {
"name": "room_type",
"type": "text",
"unsigned": false,
"autoincrement": false,
"primary": false,
"nullable": false,
"enumItems": [
"quad",
"triple",
"double",
"infant"
],
"mappedType": "enum"
},
"created_at": { "created_at": {
"name": "created_at", "name": "created_at",
"type": "timestamptz", "type": "timestamptz",
@@ -3459,11 +3422,21 @@
"mappedType": "datetime" "mappedType": "datetime"
} }
}, },
"name": "order_detail", "name": "package_consult_session",
"schema": "public", "schema": "public",
"indexes": [ "indexes": [
{ {
"keyName": "order_detail_pkey", "columnNames": [
"code"
],
"composite": false,
"keyName": "package_consult_session_code_unique",
"constraint": true,
"primary": false,
"unique": true
},
{
"keyName": "package_consult_session_pkey",
"columnNames": [ "columnNames": [
"id" "id"
], ],
@@ -3475,16 +3448,16 @@
], ],
"checks": [], "checks": [],
"foreignKeys": { "foreignKeys": {
"order_detail_order_id_foreign": { "package_consult_session_package_id_foreign": {
"constraintName": "order_detail_order_id_foreign", "constraintName": "package_consult_session_package_id_foreign",
"columnNames": [ "columnNames": [
"order_id" "package_id"
], ],
"localTableName": "public.order_detail", "localTableName": "public.package_consult_session",
"referencedColumnNames": [ "referencedColumnNames": [
"id" "id"
], ],
"referencedTableName": "public.order", "referencedTableName": "public.package",
"updateRule": "cascade" "updateRule": "cascade"
} }
}, },

View File

@@ -0,0 +1,113 @@
import { Migration } from "@mikro-orm/migrations";
export class Migration20260108134605 extends Migration {
override async up(): Promise<void> {
const knex = this.getKnex();
knex.schema.dropTable("order_detail");
await knex.schema.alterTable("order", (table) => {
table.dropColumn("verification_id");
table.dropColumn("partner_id");
});
await knex.raw(`
create type "order_type" as enum (
'booking_seat',
'down_payment'
);
`);
await knex.raw(`
create type "kit" as enum (
'minimal',
'without_suitcase',
'full'
);
`);
await knex.schema.alterTable("order", (table) => {
table.integer("quad").notNullable();
table.integer("triple").notNullable();
table.integer("double").notNullable();
table.integer("infant").notNullable();
table
.enum("type", null, {
useNative: true,
existingType: true,
enumName: "order_type",
})
.notNullable();
table.integer("quad_down_payment_percentage").notNullable();
table.integer("triple_down_payment_percentage").notNullable();
table.integer("double_down_payment_percentage").notNullable();
table.integer("infant_down_payment_percentage").notNullable();
table
.enum("kit", null, {
useNative: true,
existingType: true,
enumName: "kit",
})
.notNullable();
table.integer("vaccine").notNullable();
});
}
override async down(): Promise<void> {
const knex = this.getKnex();
await knex.schema.alterTable("order", (table) => {
table.dropColumn("quad");
table.dropColumn("triple");
table.dropColumn("double");
table.dropColumn("infant");
table.dropColumn("type");
table.dropColumn("quad_down_payment_percentage");
table.dropColumn("triple_down_payment_percentage");
table.dropColumn("double_down_payment_percentage");
table.dropColumn("infant_down_payment_percentage");
table.dropColumn("kit");
table.dropColumn("vaccine");
});
await knex.raw(`drop type "kit";`);
await knex.raw(`drop type "order_type";`);
await knex.schema.alterTable("order", (table) => {
table.string("verification_id", 30).nullable();
table.string("partner_id", 30).nullable();
table
.foreign("verification_id", "order_verification_id_foreign")
.references("verification.id")
.onUpdate("NO ACTION")
.onDelete("SET NULL");
table
.foreign("partner_id", "order_partner_id_foreign")
.references("partner.id")
.onUpdate("NO ACTION")
.onDelete("SET NULL");
});
await knex.schema.createTable("order_detail", (table) => {
// Columns
table.string("id", 30).notNullable();
table.string("order_id", 30).notNullable();
table
.enum("room_type", null, {
useNative: true,
existingType: true,
enumName: "room_type",
})
.notNullable();
table.timestamp("created_at", { useTz: true }).notNullable();
table.timestamp("updated_at", { useTz: true }).notNullable();
// Constraints
table.primary(["id"], { constraintName: "order_detail_pkey" });
table
.foreign("order_id", "order_detail_order_id_foreign")
.references("order.id")
.onUpdate("NO ACTION")
.onDelete("CASCADE");
});
}
}