Compare commits
10 Commits
85d9fb3086
...
4ad66f46cb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ad66f46cb | ||
|
|
ce354a948d | ||
|
|
8bcf0457de | ||
|
|
3349f52da1 | ||
|
|
2ad76e94d4 | ||
|
|
6e9624751e | ||
|
|
1a3a0be78d | ||
|
|
d5a153702d | ||
|
|
d95811ad83 | ||
|
|
4124d9bf19 |
@@ -5,12 +5,12 @@ export const paginationQuerySchema = z.object({
|
|||||||
page: z.coerce
|
page: z.coerce
|
||||||
.number("Must be number.")
|
.number("Must be number.")
|
||||||
.int("Must be integer.")
|
.int("Must be integer.")
|
||||||
.min(1, "Minimum 1.")
|
.min(1, "Min 1.")
|
||||||
.default(1),
|
.default(1),
|
||||||
per_page: z.coerce
|
per_page: z.coerce
|
||||||
.number("Must be number.")
|
.number("Must be number.")
|
||||||
.int("Must be integer.")
|
.int("Must be integer.")
|
||||||
.min(1, "Minimum 1.")
|
.min(1, "Min 1.")
|
||||||
.default(100),
|
.default(100),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { PaymentError } from "@/common/errors/payment.error";
|
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 { OrderDetail } from "@/database/entities/order-detail.entity";
|
|
||||||
import type { Order } from "@/database/entities/order.entity";
|
import type { Order } from "@/database/entities/order.entity";
|
||||||
import { RoomType } from "@/database/enums/room-type.enum";
|
import { Kit } from "@/database/enums/kit.enum";
|
||||||
|
import { OrderType } from "@/database/enums/order-type.enum";
|
||||||
|
|
||||||
type CreateTransactionResponseSuccess = {
|
type CreateTransactionResponseSuccess = {
|
||||||
token: string;
|
token: string;
|
||||||
@@ -13,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 = 750_000;
|
||||||
|
const FULL_KIT_PRICE = 1_500_000;
|
||||||
|
const VACCINE_PRICE = 500_000;
|
||||||
|
|
||||||
export class MidtransPaymentService extends AbstractPaymentService {
|
export class MidtransPaymentService extends AbstractPaymentService {
|
||||||
private readonly _basicAuth: string;
|
private readonly _basicAuth: string;
|
||||||
|
|
||||||
@@ -22,41 +28,66 @@ export class MidtransPaymentService extends AbstractPaymentService {
|
|||||||
this._basicAuth = `Basic ${Buffer.from(`${midtransConfig.serverKey}:`).toBase64()}`;
|
this._basicAuth = `Basic ${Buffer.from(`${midtransConfig.serverKey}:`).toBase64()}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateOrderDetailsPrice(orderDetails: OrderDetail[]): number {
|
|
||||||
let price = 0;
|
|
||||||
for (const orderDetail of orderDetails) {
|
|
||||||
switch (orderDetail.roomType) {
|
|
||||||
case RoomType.double:
|
|
||||||
price += orderDetail.order.package.doublePrice;
|
|
||||||
break;
|
|
||||||
case RoomType.triple:
|
|
||||||
price += orderDetail.order.package.triplePrice;
|
|
||||||
break;
|
|
||||||
case RoomType.quad:
|
|
||||||
price += orderDetail.order.package.quadPrice;
|
|
||||||
break;
|
|
||||||
case RoomType.infant:
|
|
||||||
price += orderDetail.order.package.infantPrice ?? 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return price;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createPaymentUrl(order: Order): Promise<string> {
|
public async createPaymentUrl(order: Order): Promise<string> {
|
||||||
const doubleOrderDetails = order.details.filter(
|
const quantity = order.quad + order.triple + order.double + order.infant;
|
||||||
(orderDetail) => orderDetail.roomType === RoomType.double,
|
|
||||||
);
|
let price = 0;
|
||||||
const tripleOrderDetails = order.details.filter(
|
if (order.type === OrderType.bookingSeat) {
|
||||||
(orderDetail) => orderDetail.roomType === RoomType.double,
|
price += BOOKING_SEAT_PRICE * quantity;
|
||||||
);
|
} else if (order.type === OrderType.downPayment) {
|
||||||
const quadOrderDetails = order.details.filter(
|
if (order.quadDownPaymentPercentage > 0) {
|
||||||
(orderDetail) => orderDetail.roomType === RoomType.double,
|
price +=
|
||||||
);
|
Math.ceil(
|
||||||
const infantOrderDetails = order.details.filter(
|
((order.quadDownPaymentPercentage / 100) *
|
||||||
(orderDetail) => orderDetail.roomType === RoomType.double,
|
(order.package.quadPrice - order.package.quadDiscount)) /
|
||||||
);
|
100_000,
|
||||||
|
) *
|
||||||
|
100_000 *
|
||||||
|
order.quad;
|
||||||
|
}
|
||||||
|
if (order.tripleDownPaymentPercentage > 0) {
|
||||||
|
price +=
|
||||||
|
Math.ceil(
|
||||||
|
((order.tripleDownPaymentPercentage / 100) *
|
||||||
|
(order.package.triplePrice - order.package.tripleDiscount)) /
|
||||||
|
100_000,
|
||||||
|
) *
|
||||||
|
100_000 *
|
||||||
|
order.triple;
|
||||||
|
}
|
||||||
|
if (order.doubleDownPaymentPercentage > 0) {
|
||||||
|
price +=
|
||||||
|
Math.ceil(
|
||||||
|
((order.doubleDownPaymentPercentage / 100) *
|
||||||
|
(order.package.doublePrice - order.package.doubleDiscount)) /
|
||||||
|
100_000,
|
||||||
|
) *
|
||||||
|
100_000 *
|
||||||
|
order.double;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
order.infantDownPaymentPercentage > 0 &&
|
||||||
|
order.package.infantPrice !== null &&
|
||||||
|
order.package.infantDiscount !== null
|
||||||
|
) {
|
||||||
|
price +=
|
||||||
|
Math.ceil(
|
||||||
|
((order.infantDownPaymentPercentage / 100) *
|
||||||
|
(order.package.infantPrice - order.package.infantDiscount)) /
|
||||||
|
100_000,
|
||||||
|
) *
|
||||||
|
100_000 *
|
||||||
|
order.infant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (order.kit === Kit.minimal) {
|
||||||
|
price += MINIMAL_KIT_PRICE * quantity;
|
||||||
|
} else if (order.kit === Kit.withoutSuitcase) {
|
||||||
|
price += WITHOUT_SUITCASE_KIT_PRICE * quantity;
|
||||||
|
} else if (order.kit === Kit.full) {
|
||||||
|
price += FULL_KIT_PRICE * quantity;
|
||||||
|
}
|
||||||
|
price += VACCINE_PRICE * order.vaccine;
|
||||||
|
|
||||||
const response = await fetch(`${midtransConfig.baseUrl}/transactions`, {
|
const response = await fetch(`${midtransConfig.baseUrl}/transactions`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -68,55 +99,18 @@ 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.calculateOrderDetailsPrice(
|
gross_amount: price,
|
||||||
order.details.getItems(),
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
item_details: [
|
item_details: [
|
||||||
doubleOrderDetails.length > 0
|
{
|
||||||
? {
|
id: order.id,
|
||||||
id: doubleOrderDetails[0].id,
|
price,
|
||||||
price: order.package.doublePrice,
|
quantity: 1,
|
||||||
quantity: doubleOrderDetails.length,
|
name: `${order.package.package.name}`,
|
||||||
name: `${order.package.package.name} / Double`,
|
|
||||||
brand: "GoUmrah",
|
brand: "GoUmrah",
|
||||||
category: "Paket",
|
category: "Paket",
|
||||||
merchant_name: "GoUmrah",
|
merchant_name: "GoUmrah",
|
||||||
}
|
},
|
||||||
: undefined,
|
|
||||||
tripleOrderDetails.length > 0
|
|
||||||
? {
|
|
||||||
id: tripleOrderDetails[0].id,
|
|
||||||
price: order.package.triplePrice,
|
|
||||||
quantity: tripleOrderDetails.length,
|
|
||||||
name: `${order.package.package.name} / Triple`,
|
|
||||||
brand: "GoUmrah",
|
|
||||||
category: "Paket",
|
|
||||||
merchant_name: "GoUmrah",
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
quadOrderDetails.length > 0
|
|
||||||
? {
|
|
||||||
id: quadOrderDetails[0].id,
|
|
||||||
price: order.package.quadPrice,
|
|
||||||
quantity: quadOrderDetails.length,
|
|
||||||
name: `${order.package.package.name} / Quad`,
|
|
||||||
brand: "GoUmrah",
|
|
||||||
category: "Paket",
|
|
||||||
merchant_name: "GoUmrah",
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
infantOrderDetails.length > 0
|
|
||||||
? {
|
|
||||||
id: infantOrderDetails[0].id,
|
|
||||||
price: order.package.infantPrice,
|
|
||||||
quantity: infantOrderDetails.length,
|
|
||||||
name: `${order.package.package.name} / Infant`,
|
|
||||||
brand: "GoUmrah",
|
|
||||||
category: "Paket",
|
|
||||||
merchant_name: "GoUmrah",
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
],
|
],
|
||||||
customer_details: {
|
customer_details: {
|
||||||
first_name: order.name,
|
first_name: order.name,
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ export const _env = z
|
|||||||
SERVER_PORT: z.coerce
|
SERVER_PORT: z.coerce
|
||||||
.number("Must be number.")
|
.number("Must be number.")
|
||||||
.int("Must be integer.")
|
.int("Must be integer.")
|
||||||
.min(0, "Min 0."),
|
.min(0, "Minimum0."),
|
||||||
|
|
||||||
DATABASE_HOST: z.string("Must be string.").nonempty("Must not empty."),
|
DATABASE_HOST: z.string("Must be string.").nonempty("Must not empty."),
|
||||||
DATABASE_PORT: z.coerce
|
DATABASE_PORT: z.coerce
|
||||||
.number("Must be number.")
|
.number("Must be number.")
|
||||||
.int("Must be integer.")
|
.int("Must be integer.")
|
||||||
.min(0, "Min 0."),
|
.min(0, "Minimum0."),
|
||||||
DATABASE_USERNAME: z.string("Must be string.").nonempty("Must not empty."),
|
DATABASE_USERNAME: z.string("Must be string.").nonempty("Must not empty."),
|
||||||
DATABASE_PASSWORD: z.string("Must be string.").nonempty("Must not empty."),
|
DATABASE_PASSWORD: z.string("Must be string.").nonempty("Must not empty."),
|
||||||
DATABASE_NAME: z.string("Must be string.").nonempty("Must not empty."),
|
DATABASE_NAME: z.string("Must be string.").nonempty("Must not empty."),
|
||||||
@@ -49,7 +49,7 @@ export const _env = z
|
|||||||
MAIL_PORT: z.coerce
|
MAIL_PORT: z.coerce
|
||||||
.number("Must be number.")
|
.number("Must be number.")
|
||||||
.int("Must be integer.")
|
.int("Must be integer.")
|
||||||
.min(0, "Min 0."),
|
.min(0, "Minimum0."),
|
||||||
MAIL_USERNAME: z.string("Must be string.").nonempty("Must not empty."),
|
MAIL_USERNAME: z.string("Must be string.").nonempty("Must not empty."),
|
||||||
MAIL_PASSWORD: z.string("Must be string.").nonempty("Must not empty."),
|
MAIL_PASSWORD: z.string("Must be string.").nonempty("Must not empty."),
|
||||||
|
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import { Order } from "@/database/entities/order.entity";
|
|
||||||
import { RoomType } from "@/database/enums/room-type.enum";
|
|
||||||
import {
|
|
||||||
Entity,
|
|
||||||
Enum,
|
|
||||||
ManyToOne,
|
|
||||||
PrimaryKey,
|
|
||||||
Property,
|
|
||||||
type Rel,
|
|
||||||
} from "@mikro-orm/core";
|
|
||||||
|
|
||||||
@Entity()
|
|
||||||
export class OrderDetail {
|
|
||||||
@PrimaryKey({ type: "varchar", length: 30 })
|
|
||||||
id!: string;
|
|
||||||
|
|
||||||
@ManyToOne(() => Order)
|
|
||||||
order!: Rel<Order>;
|
|
||||||
|
|
||||||
@Enum({ items: () => RoomType })
|
|
||||||
roomType!: RoomType;
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
type: "timestamp",
|
|
||||||
onCreate: () => new Date(),
|
|
||||||
})
|
|
||||||
createdAt!: Date;
|
|
||||||
|
|
||||||
@Property({
|
|
||||||
type: "timestamp",
|
|
||||||
onCreate: () => new Date(),
|
|
||||||
onUpdate: () => new Date(),
|
|
||||||
})
|
|
||||||
updatedAt!: Date;
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
import { OrderDetail } from "@/database/entities/order-detail.entity";
|
|
||||||
import { PackageDetail } from "@/database/entities/package-detail.entity";
|
import { PackageDetail } from "@/database/entities/package-detail.entity";
|
||||||
import { Partner } from "@/database/entities/partner.entity";
|
import { Kit } from "@/database/enums/kit.enum";
|
||||||
import { Verification } from "@/database/entities/verification.entity";
|
import { OrderType } from "@/database/enums/order-type.enum";
|
||||||
import {
|
import {
|
||||||
Collection,
|
|
||||||
Entity,
|
Entity,
|
||||||
|
Enum,
|
||||||
ManyToOne,
|
ManyToOne,
|
||||||
OneToMany,
|
|
||||||
PrimaryKey,
|
PrimaryKey,
|
||||||
Property,
|
Property,
|
||||||
type Rel,
|
type Rel,
|
||||||
@@ -20,18 +18,45 @@ export class Order {
|
|||||||
@ManyToOne(() => PackageDetail)
|
@ManyToOne(() => PackageDetail)
|
||||||
package!: Rel<PackageDetail>;
|
package!: Rel<PackageDetail>;
|
||||||
|
|
||||||
|
@Property({ type: "int", unsigned: true })
|
||||||
|
quad!: number;
|
||||||
|
|
||||||
|
@Property({ type: "int", unsigned: true })
|
||||||
|
triple!: number;
|
||||||
|
|
||||||
|
@Property({ type: "int", unsigned: true })
|
||||||
|
double!: number;
|
||||||
|
|
||||||
|
@Property({ type: "int", unsigned: true })
|
||||||
|
infant!: number;
|
||||||
|
|
||||||
|
@Enum({ items: () => OrderType })
|
||||||
|
type!: OrderType;
|
||||||
|
|
||||||
|
@Property({ type: "int", unsigned: true })
|
||||||
|
quadDownPaymentPercentage!: number;
|
||||||
|
|
||||||
|
@Property({ type: "int", unsigned: true })
|
||||||
|
tripleDownPaymentPercentage!: number;
|
||||||
|
|
||||||
|
@Property({ type: "int", unsigned: true })
|
||||||
|
doubleDownPaymentPercentage!: number;
|
||||||
|
|
||||||
|
@Property({ type: "int", unsigned: true })
|
||||||
|
infantDownPaymentPercentage!: number;
|
||||||
|
|
||||||
|
@Enum({ items: () => Kit })
|
||||||
|
kit!: Kit;
|
||||||
|
|
||||||
|
@Property({ type: "int", unsigned: true })
|
||||||
|
vaccine!: number;
|
||||||
|
|
||||||
@Property({ type: "varchar", length: 100 })
|
@Property({ type: "varchar", length: 100 })
|
||||||
name!: string;
|
name!: string;
|
||||||
|
|
||||||
@Property({ type: "varchar", length: 20 })
|
@Property({ type: "varchar", length: 20 })
|
||||||
whatsapp!: string;
|
whatsapp!: string;
|
||||||
|
|
||||||
@ManyToOne(() => Verification, { nullable: true })
|
|
||||||
verification!: Rel<Verification> | null;
|
|
||||||
|
|
||||||
@ManyToOne(() => Partner, { nullable: true })
|
|
||||||
partner!: Rel<Partner | null>;
|
|
||||||
|
|
||||||
@Property({ type: "timestamp", nullable: true })
|
@Property({ type: "timestamp", nullable: true })
|
||||||
expiredAt!: Date | null;
|
expiredAt!: Date | null;
|
||||||
|
|
||||||
@@ -53,9 +78,4 @@ export class Order {
|
|||||||
onUpdate: () => new Date(),
|
onUpdate: () => new Date(),
|
||||||
})
|
})
|
||||||
updatedAt!: Date;
|
updatedAt!: Date;
|
||||||
|
|
||||||
// Collections
|
|
||||||
|
|
||||||
@OneToMany(() => OrderDetail, (orderDetail) => orderDetail.order)
|
|
||||||
details = new Collection<OrderDetail>(this);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
}
|
||||||
|
|||||||
5
src/database/enums/kit.enum.ts
Normal file
5
src/database/enums/kit.enum.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export enum Kit {
|
||||||
|
minimal = "minimal",
|
||||||
|
withoutSuitcase = "without_suitcase",
|
||||||
|
full = "full",
|
||||||
|
}
|
||||||
4
src/database/enums/order-type.enum.ts
Normal file
4
src/database/enums/order-type.enum.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export enum OrderType {
|
||||||
|
bookingSeat = "booking_seat",
|
||||||
|
downPayment = "down_payment",
|
||||||
|
}
|
||||||
@@ -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": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": false,
|
||||||
|
"mappedType": "integer"
|
||||||
|
},
|
||||||
|
"triple": {
|
||||||
|
"name": "triple",
|
||||||
|
"type": "int",
|
||||||
|
"unsigned": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": false,
|
||||||
|
"mappedType": "integer"
|
||||||
|
},
|
||||||
|
"double": {
|
||||||
|
"name": "double",
|
||||||
|
"type": "int",
|
||||||
|
"unsigned": true,
|
||||||
|
"autoincrement": false,
|
||||||
|
"primary": false,
|
||||||
|
"nullable": false,
|
||||||
|
"mappedType": "integer"
|
||||||
|
},
|
||||||
|
"infant": {
|
||||||
|
"name": "infant",
|
||||||
|
"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": 6,
|
"enumItems": [
|
||||||
"mappedType": "datetime"
|
"booking_seat",
|
||||||
|
"down_payment"
|
||||||
|
],
|
||||||
|
"mappedType": "enum"
|
||||||
},
|
},
|
||||||
"updated_at": {
|
"quad_down_payment_percentage": {
|
||||||
"name": "updated_at",
|
"name": "quad_down_payment_percentage",
|
||||||
"type": "timestamptz",
|
"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,
|
"unsigned": false,
|
||||||
"autoincrement": false,
|
"autoincrement": false,
|
||||||
"primary": false,
|
"primary": false,
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
"length": 6,
|
"enumItems": [
|
||||||
"mappedType": "datetime"
|
"minimal",
|
||||||
}
|
"without_suitcase",
|
||||||
|
"full"
|
||||||
|
],
|
||||||
|
"mappedType": "enum"
|
||||||
},
|
},
|
||||||
"name": "package_consult_session",
|
"vaccine": {
|
||||||
"schema": "public",
|
"name": "vaccine",
|
||||||
"indexes": [
|
"type": "int",
|
||||||
{
|
"unsigned": true,
|
||||||
"columnNames": [
|
|
||||||
"code"
|
|
||||||
],
|
|
||||||
"composite": false,
|
|
||||||
"keyName": "package_consult_session_code_unique",
|
|
||||||
"constraint": true,
|
|
||||||
"primary": false,
|
|
||||||
"unique": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"keyName": "package_consult_session_pkey",
|
|
||||||
"columnNames": [
|
|
||||||
"id"
|
|
||||||
],
|
|
||||||
"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": {
|
|
||||||
"name": "package_id",
|
|
||||||
"type": "varchar(30)",
|
|
||||||
"unsigned": false,
|
|
||||||
"autoincrement": false,
|
|
||||||
"primary": false,
|
|
||||||
"nullable": false,
|
|
||||||
"length": 30,
|
|
||||||
"mappedType": "string"
|
|
||||||
},
|
},
|
||||||
"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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
113
src/database/migrations/Migration20260108134605.ts
Normal file
113
src/database/migrations/Migration20260108134605.ts
Normal 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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
29
src/database/migrations/Migration20260108142003.ts
Normal file
29
src/database/migrations/Migration20260108142003.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { Migration } from "@mikro-orm/migrations";
|
||||||
|
|
||||||
|
export class Migration20260108142003 extends Migration {
|
||||||
|
override async up(): Promise<void> {
|
||||||
|
const knex = this.getKnex();
|
||||||
|
|
||||||
|
await knex.schema.alterTable("order", (table) => {
|
||||||
|
table.dropForeign("package_id", "order_package_id_foreign");
|
||||||
|
table
|
||||||
|
.foreign("package_id", "order_package_id_foreign")
|
||||||
|
.references("package_detail.id")
|
||||||
|
.onUpdate("NO ACTION")
|
||||||
|
.onDelete("CASCADE");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
override async down(): Promise<void> {
|
||||||
|
const knex = this.getKnex();
|
||||||
|
|
||||||
|
await knex.schema.alterTable("order", (table) => {
|
||||||
|
table.dropForeign("package_id", "order_package_id_foreign");
|
||||||
|
table
|
||||||
|
.foreign("package_id", "order_package_id_foreign")
|
||||||
|
.references("package.id")
|
||||||
|
.onUpdate("NO ACTION")
|
||||||
|
.onDelete("CASCADE");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,8 +14,8 @@ export const airlineRequestSchema = z.object({
|
|||||||
skytrax_rating: z
|
skytrax_rating: z
|
||||||
.number("Must be number.")
|
.number("Must be number.")
|
||||||
.int("Must be integer.")
|
.int("Must be integer.")
|
||||||
.min(1, "Minimum 1.")
|
.min(1, "Min 1.")
|
||||||
.max(5, "Maximum 5."),
|
.max(5, "Max 5."),
|
||||||
skytrax_type: z.enum(
|
skytrax_type: z.enum(
|
||||||
SkytraxType,
|
SkytraxType,
|
||||||
"Must be either 'full_service' or 'low_cost'.",
|
"Must be either 'full_service' or 'low_cost'.",
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ export const hotelRequestSchema = z.object({
|
|||||||
star: z
|
star: z
|
||||||
.number("Must be number.")
|
.number("Must be number.")
|
||||||
.int("Must be integer.")
|
.int("Must be integer.")
|
||||||
.min(1, "Minimum 1.")
|
.min(1, "Min 1.")
|
||||||
.max(7, "Maximum 7."),
|
.max(7, "Max 7."),
|
||||||
images: z
|
images: z
|
||||||
.array(
|
.array(
|
||||||
z.base64("Must be base64 string.").nonempty("Must not empty."),
|
z.base64("Must be base64 string.").nonempty("Must not empty."),
|
||||||
|
|||||||
@@ -12,22 +12,15 @@ import type {
|
|||||||
ListResponse,
|
ListResponse,
|
||||||
SingleResponse,
|
SingleResponse,
|
||||||
} from "@/common/types";
|
} from "@/common/types";
|
||||||
import { generateRandomCode } from "@/common/utils";
|
|
||||||
import { OrderDetail } from "@/database/entities/order-detail.entity";
|
|
||||||
import { Order } from "@/database/entities/order.entity";
|
import { Order } from "@/database/entities/order.entity";
|
||||||
import { PackageDetail } from "@/database/entities/package-detail.entity";
|
import { PackageDetail } from "@/database/entities/package-detail.entity";
|
||||||
import { Partner } from "@/database/entities/partner.entity";
|
|
||||||
import { Verification } from "@/database/entities/verification.entity";
|
|
||||||
import { VerificationType } from "@/database/enums/verification-type.enum";
|
|
||||||
import { orm } from "@/database/orm";
|
import { orm } from "@/database/orm";
|
||||||
import type { OrderMapper } from "@/modules/order/order.mapper";
|
import type { OrderMapper } from "@/modules/order/order.mapper";
|
||||||
import {
|
import {
|
||||||
orderParamsSchema,
|
orderParamsSchema,
|
||||||
orderRequestSchema,
|
orderRequestSchema,
|
||||||
orderVerifyRequestSchema,
|
|
||||||
} from "@/modules/order/order.schemas";
|
} from "@/modules/order/order.schemas";
|
||||||
import type { OrderResponse } from "@/modules/order/order.types";
|
import type { OrderResponse } from "@/modules/order/order.types";
|
||||||
import * as dateFns from "date-fns";
|
|
||||||
import { Router, type Request, type Response } from "express";
|
import { Router, type Request, type Response } from "express";
|
||||||
import { ulid } from "ulid";
|
import { ulid } from "ulid";
|
||||||
|
|
||||||
@@ -47,9 +40,15 @@ export class OrderController extends Controller {
|
|||||||
}
|
}
|
||||||
const body = parseBodyResult.data;
|
const body = parseBodyResult.data;
|
||||||
|
|
||||||
const packageDetail = await orm.em.findOne(PackageDetail, {
|
const packageDetail = await orm.em.findOne(
|
||||||
|
PackageDetail,
|
||||||
|
{
|
||||||
id: body.package_id,
|
id: body.package_id,
|
||||||
});
|
},
|
||||||
|
{
|
||||||
|
populate: ["*"],
|
||||||
|
},
|
||||||
|
);
|
||||||
if (!packageDetail) {
|
if (!packageDetail) {
|
||||||
return res.status(404).json({
|
return res.status(404).json({
|
||||||
data: null,
|
data: null,
|
||||||
@@ -63,22 +62,22 @@ export class OrderController extends Controller {
|
|||||||
} satisfies ErrorResponse);
|
} satisfies ErrorResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
const verification = orm.em.create(Verification, {
|
|
||||||
id: ulid(),
|
|
||||||
code: generateRandomCode(6, "0123456789"),
|
|
||||||
type: VerificationType.createOrder,
|
|
||||||
expiredAt: dateFns.addHours(new Date(), 1),
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const order = orm.em.create(Order, {
|
const order = orm.em.create(Order, {
|
||||||
id: ulid(),
|
id: ulid(),
|
||||||
package: packageDetail,
|
package: packageDetail,
|
||||||
|
quad: body.quad,
|
||||||
|
triple: body.triple,
|
||||||
|
double: body.double,
|
||||||
|
infant: body.infant,
|
||||||
|
type: body.type,
|
||||||
|
quadDownPaymentPercentage: body.quad_down_payment_percentage,
|
||||||
|
tripleDownPaymentPercentage: body.triple_down_payment_percentage,
|
||||||
|
doubleDownPaymentPercentage: body.double_down_payment_percentage,
|
||||||
|
infantDownPaymentPercentage: body.infant_down_payment_percentage,
|
||||||
|
kit: body.kit,
|
||||||
|
vaccine: body.vaccine,
|
||||||
name: body.name,
|
name: body.name,
|
||||||
whatsapp: body.whatsapp,
|
whatsapp: body.whatsapp,
|
||||||
verification,
|
|
||||||
partner: null,
|
|
||||||
expiredAt: null,
|
expiredAt: null,
|
||||||
purchasedAt: null,
|
purchasedAt: null,
|
||||||
finishedAt: null,
|
finishedAt: null,
|
||||||
@@ -86,27 +85,21 @@ export class OrderController extends Controller {
|
|||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
});
|
});
|
||||||
|
|
||||||
for (const roomType of body.room_types) {
|
|
||||||
order.details.add(
|
|
||||||
orm.em.create(OrderDetail, {
|
|
||||||
id: ulid(),
|
|
||||||
order,
|
|
||||||
roomType,
|
|
||||||
createdAt: new Date(),
|
|
||||||
updatedAt: new Date(),
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await orm.em.flush();
|
await orm.em.flush();
|
||||||
|
|
||||||
|
const paymentUrl = await this.paymentService.createPaymentUrl(order);
|
||||||
|
|
||||||
return res.status(201).json({
|
return res.status(201).json({
|
||||||
data: {
|
data: {
|
||||||
message:
|
...this.mapper.mapEntityToResponse(order),
|
||||||
"Order created successfully. Please check your email for verification.",
|
payment_url: paymentUrl,
|
||||||
},
|
},
|
||||||
errors: null,
|
errors: null,
|
||||||
} satisfies SingleResponse);
|
} satisfies SingleResponse<
|
||||||
|
OrderResponse & {
|
||||||
|
payment_url: string;
|
||||||
|
}
|
||||||
|
>);
|
||||||
}
|
}
|
||||||
|
|
||||||
async list(_req: Request, res: Response) {
|
async list(_req: Request, res: Response) {
|
||||||
@@ -123,9 +116,12 @@ export class OrderController extends Controller {
|
|||||||
const orders = await orm.em.find(
|
const orders = await orm.em.find(
|
||||||
Order,
|
Order,
|
||||||
{
|
{
|
||||||
verification: null,
|
package: {
|
||||||
|
package: {
|
||||||
partner: req.partner,
|
partner: req.partner,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
limit: query.per_page,
|
limit: query.per_page,
|
||||||
offset: (query.page - 1) * query.per_page,
|
offset: (query.page - 1) * query.per_page,
|
||||||
@@ -159,9 +155,12 @@ export class OrderController extends Controller {
|
|||||||
Order,
|
Order,
|
||||||
{
|
{
|
||||||
id: params.id,
|
id: params.id,
|
||||||
verification: null,
|
package: {
|
||||||
|
package: {
|
||||||
partner: req.partner,
|
partner: req.partner,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
populate: ["*"],
|
populate: ["*"],
|
||||||
},
|
},
|
||||||
@@ -198,9 +197,12 @@ export class OrderController extends Controller {
|
|||||||
Order,
|
Order,
|
||||||
{
|
{
|
||||||
id: params.id,
|
id: params.id,
|
||||||
verification: null,
|
package: {
|
||||||
|
package: {
|
||||||
partner: req.partner,
|
partner: req.partner,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
populate: ["*"],
|
populate: ["*"],
|
||||||
},
|
},
|
||||||
@@ -229,94 +231,6 @@ export class OrderController extends Controller {
|
|||||||
} satisfies SingleResponse<OrderResponse>);
|
} satisfies SingleResponse<OrderResponse>);
|
||||||
}
|
}
|
||||||
|
|
||||||
async verify(req: Request, res: Response) {
|
|
||||||
const parseParamsResult = orderParamsSchema.safeParse(req.params);
|
|
||||||
if (!parseParamsResult.success) {
|
|
||||||
return this.handleZodError(parseParamsResult.error, res, "params");
|
|
||||||
}
|
|
||||||
const params = parseParamsResult.data;
|
|
||||||
|
|
||||||
const parseBodyResult = orderVerifyRequestSchema.safeParse(req.body);
|
|
||||||
if (!parseBodyResult.success) {
|
|
||||||
return this.handleZodError(parseBodyResult.error, res, "body");
|
|
||||||
}
|
|
||||||
const body = parseBodyResult.data;
|
|
||||||
|
|
||||||
const order = await orm.em.findOne(
|
|
||||||
Order,
|
|
||||||
{ id: params.id },
|
|
||||||
{
|
|
||||||
populate: ["*"],
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (!order) {
|
|
||||||
return res.status(404).json({
|
|
||||||
data: null,
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
path: "id",
|
|
||||||
location: "params",
|
|
||||||
message: "Order not found.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
} satisfies ErrorResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (order.verification === null) {
|
|
||||||
return res.status(400).json({
|
|
||||||
data: null,
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
message: "Order is already verified.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
} satisfies ErrorResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (order.verification.code !== body.code) {
|
|
||||||
return res.status(400).json({
|
|
||||||
data: null,
|
|
||||||
errors: [
|
|
||||||
{
|
|
||||||
path: "code",
|
|
||||||
location: "body",
|
|
||||||
message: "Incorrect.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
} satisfies ErrorResponse);
|
|
||||||
}
|
|
||||||
|
|
||||||
orm.em.remove(order.verification);
|
|
||||||
|
|
||||||
const partners = await orm.em.findAll(Partner, { populate: ["*"] });
|
|
||||||
const partner = partners.toSorted(
|
|
||||||
(a, b) =>
|
|
||||||
a.orders.filter((order) => order.finishedAt === null).length -
|
|
||||||
b.orders.filter((order) => order.finishedAt === null).length,
|
|
||||||
)[0];
|
|
||||||
|
|
||||||
order.verification = null;
|
|
||||||
order.partner = partner;
|
|
||||||
order.expiredAt = dateFns.addHours(new Date(), 24);
|
|
||||||
order.updatedAt = new Date();
|
|
||||||
|
|
||||||
await orm.em.flush();
|
|
||||||
|
|
||||||
const paymentUrl = await this.paymentService.createPaymentUrl(order);
|
|
||||||
|
|
||||||
return res.status(200).json({
|
|
||||||
data: {
|
|
||||||
...this.mapper.mapEntityToResponse(order),
|
|
||||||
payment_url: paymentUrl,
|
|
||||||
},
|
|
||||||
errors: null,
|
|
||||||
} satisfies SingleResponse<
|
|
||||||
OrderResponse & {
|
|
||||||
payment_url: string;
|
|
||||||
}
|
|
||||||
>);
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(_req: Request, res: Response) {
|
async delete(_req: Request, res: Response) {
|
||||||
const req = _req as Request & PartnerRequestPlugin;
|
const req = _req as Request & PartnerRequestPlugin;
|
||||||
|
|
||||||
@@ -328,7 +242,14 @@ export class OrderController extends Controller {
|
|||||||
|
|
||||||
const order = await orm.em.findOne(
|
const order = await orm.em.findOne(
|
||||||
Order,
|
Order,
|
||||||
{ id: params.id, verification: null, partner: req.partner },
|
{
|
||||||
|
id: params.id,
|
||||||
|
package: {
|
||||||
|
package: {
|
||||||
|
partner: req.partner,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
populate: ["*"],
|
populate: ["*"],
|
||||||
},
|
},
|
||||||
@@ -372,11 +293,6 @@ export class OrderController extends Controller {
|
|||||||
isPartnerMiddleware(this.jwtService),
|
isPartnerMiddleware(this.jwtService),
|
||||||
this.finish.bind(this),
|
this.finish.bind(this),
|
||||||
);
|
);
|
||||||
router.put(
|
|
||||||
"/:id/verify",
|
|
||||||
createOrmContextMiddleware,
|
|
||||||
this.verify.bind(this),
|
|
||||||
);
|
|
||||||
router.delete(
|
router.delete(
|
||||||
"/:id",
|
"/:id",
|
||||||
createOrmContextMiddleware,
|
createOrmContextMiddleware,
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { Order } from "@/database/entities/order.entity";
|
import type { Order } from "@/database/entities/order.entity";
|
||||||
import { RoomType } from "@/database/enums/room-type.enum";
|
|
||||||
import type { OrderResponse } from "@/modules/order/order.types";
|
import type { OrderResponse } from "@/modules/order/order.types";
|
||||||
import type { PackageMapper } from "@/modules/package/package.mapper";
|
import type { PackageMapper } from "@/modules/package/package.mapper";
|
||||||
import type { PartnerMapper } from "@/modules/partner/partner.mapper";
|
import type { PartnerMapper } from "@/modules/partner/partner.mapper";
|
||||||
@@ -11,44 +10,11 @@ export class OrderMapper {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
public mapEntityToResponse(order: Order): OrderResponse {
|
public mapEntityToResponse(order: Order): OrderResponse {
|
||||||
const details: OrderResponse["details"] = [];
|
|
||||||
let totalPrice = 0;
|
|
||||||
for (const detail of order.details) {
|
|
||||||
let price = 0;
|
|
||||||
switch (detail.roomType) {
|
|
||||||
case RoomType.double:
|
|
||||||
price = order.package.doublePrice;
|
|
||||||
break;
|
|
||||||
case RoomType.triple:
|
|
||||||
price = order.package.triplePrice;
|
|
||||||
break;
|
|
||||||
case RoomType.quad:
|
|
||||||
price = order.package.quadPrice;
|
|
||||||
break;
|
|
||||||
case RoomType.infant:
|
|
||||||
price = order.package.infantPrice ?? 0;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
details.push({
|
|
||||||
price,
|
|
||||||
room_type: detail.roomType,
|
|
||||||
});
|
|
||||||
|
|
||||||
totalPrice += price;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: order.id,
|
id: order.id,
|
||||||
package: this.packageMapper.mapDetailEntityToResponse(order.package),
|
package: this.packageMapper.mapDetailEntityToResponse(order.package),
|
||||||
name: order.name,
|
name: order.name,
|
||||||
whatsapp: order.whatsapp,
|
whatsapp: order.whatsapp,
|
||||||
details,
|
|
||||||
total_price: totalPrice,
|
|
||||||
is_verified: order.verification === null,
|
|
||||||
partner: order.partner
|
|
||||||
? this.partnerMapper.mapEntityToResponse(order.partner)
|
|
||||||
: null,
|
|
||||||
expired_at: order.expiredAt,
|
expired_at: order.expiredAt,
|
||||||
purchased_at: order.purchasedAt,
|
purchased_at: order.purchasedAt,
|
||||||
finished_at: order.finishedAt,
|
finished_at: order.finishedAt,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { phoneNumberSchema } from "@/common/schemas";
|
import { phoneNumberSchema } from "@/common/schemas";
|
||||||
import { RoomType } from "@/database/enums/room-type.enum";
|
import { Kit } from "@/database/enums/kit.enum";
|
||||||
|
import { OrderType } from "@/database/enums/order-type.enum";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
export const orderRequestSchema = z.object({
|
export const orderRequestSchema = z.object({
|
||||||
@@ -7,20 +8,38 @@ export const orderRequestSchema = z.object({
|
|||||||
.ulid("Must be ulid string.")
|
.ulid("Must be ulid string.")
|
||||||
.nonempty("Must not empty.")
|
.nonempty("Must not empty.")
|
||||||
.max(30, "Max 30 characters."),
|
.max(30, "Max 30 characters."),
|
||||||
|
quad: z.number("Must be number.").int("Must be integer.").min(0, "Min 0."),
|
||||||
|
triple: z.number("Must be number.").int("Must be integer.").min(0, "Min 0."),
|
||||||
|
double: z.number("Must be number.").int("Must be integer.").min(0, "Min 0."),
|
||||||
|
infant: z.number("Must be number.").int("Must be integer.").min(0, "Min 0."),
|
||||||
|
type: z.enum(OrderType, "Must be either 'booking_seat' or 'down_payment'."),
|
||||||
|
quad_down_payment_percentage: z
|
||||||
|
.number("Must be number.")
|
||||||
|
.int("Must be integer")
|
||||||
|
.min(0, "Min 0.")
|
||||||
|
.max(100, "Max 100."),
|
||||||
|
triple_down_payment_percentage: z
|
||||||
|
.number("Must be number.")
|
||||||
|
.int("Must be integer")
|
||||||
|
.min(0, "Min 0.")
|
||||||
|
.max(100, "Max 100."),
|
||||||
|
double_down_payment_percentage: z
|
||||||
|
.number("Must be number.")
|
||||||
|
.int("Must be integer")
|
||||||
|
.min(0, "Min 0.")
|
||||||
|
.max(100, "Max 100."),
|
||||||
|
infant_down_payment_percentage: z
|
||||||
|
.number("Must be number.")
|
||||||
|
.int("Must be integer")
|
||||||
|
.min(0, "Min 0.")
|
||||||
|
.max(100, "Max 100."),
|
||||||
|
kit: z.enum(Kit, "Must be either 'minimal', 'without_suitcase', or 'full'."),
|
||||||
|
vaccine: z.number("Must be number.").int("Must be integer.").min(0, "Min 0."),
|
||||||
name: z
|
name: z
|
||||||
.string("Must be string.")
|
.string("Must be string.")
|
||||||
.nonempty("Must not empty.")
|
.nonempty("Must not empty.")
|
||||||
.max(100, "Max 100 characters."),
|
.max(100, "Max 100 characters."),
|
||||||
whatsapp: phoneNumberSchema,
|
whatsapp: phoneNumberSchema,
|
||||||
room_types: z
|
|
||||||
.array(
|
|
||||||
z.enum(
|
|
||||||
RoomType,
|
|
||||||
"Must be either 'double', 'triple', 'quad', or 'infant'.",
|
|
||||||
),
|
|
||||||
"Must be array.",
|
|
||||||
)
|
|
||||||
.nonempty("Must not empty."),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const orderVerifyRequestSchema = z.object({
|
export const orderVerifyRequestSchema = z.object({
|
||||||
|
|||||||
@@ -1,10 +1,8 @@
|
|||||||
import type { RoomType } from "@/database/enums/room-type.enum";
|
|
||||||
import type {
|
import type {
|
||||||
orderParamsSchema,
|
orderParamsSchema,
|
||||||
orderRequestSchema,
|
orderRequestSchema,
|
||||||
} from "@/modules/order/order.schemas";
|
} from "@/modules/order/order.schemas";
|
||||||
import type { PackageDetailResponse } from "@/modules/package/package.types";
|
import type { PackageDetailResponse } from "@/modules/package/package.types";
|
||||||
import type { PartnerResponse } from "@/modules/partner/partner.types";
|
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
|
|
||||||
export type OrderRequest = z.infer<typeof orderRequestSchema>;
|
export type OrderRequest = z.infer<typeof orderRequestSchema>;
|
||||||
@@ -16,13 +14,6 @@ export type OrderResponse = {
|
|||||||
package: PackageDetailResponse;
|
package: PackageDetailResponse;
|
||||||
name: string;
|
name: string;
|
||||||
whatsapp: string;
|
whatsapp: string;
|
||||||
details: {
|
|
||||||
room_type: RoomType;
|
|
||||||
price: number;
|
|
||||||
}[];
|
|
||||||
total_price: number;
|
|
||||||
is_verified: boolean;
|
|
||||||
partner: PartnerResponse | null;
|
|
||||||
expired_at: Date | null;
|
expired_at: Date | null;
|
||||||
purchased_at: Date | null;
|
purchased_at: Date | null;
|
||||||
finished_at: Date | null;
|
finished_at: Date | null;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export const testimonyRequestSchema = z.object({
|
|||||||
rating: z
|
rating: z
|
||||||
.number("Must be number.")
|
.number("Must be number.")
|
||||||
.int("Must be integer.")
|
.int("Must be integer.")
|
||||||
.min(1, "Min 1.")
|
.min(1, "Minimum1.")
|
||||||
.max(5, "Max 5."),
|
.max(5, "Max 5."),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user