mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-09-09 00:08:18 +00:00
implement booking flow
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { QUERY_KEYS } from "@/constants/QUERY_KEYS";
|
||||
import { endpoint } from "@/utils/endpoint";
|
||||
import type {
|
||||
CreateFileUploadFieldDto,
|
||||
@@ -16,7 +17,6 @@ import {
|
||||
UpdateDropdownSettingDto,
|
||||
} from "@/types/dropdownSettings";
|
||||
import {
|
||||
ApproveRatePayload,
|
||||
RuleEngineListResult,
|
||||
RuleEngineRecord,
|
||||
RuleEngineResourceSlug,
|
||||
@@ -27,8 +27,14 @@ import {
|
||||
ruleEngineService,
|
||||
RuleEngineListParams,
|
||||
} from "./ruleEngine/ruleEngine.service";
|
||||
import { bookingsService, BookingListFilter } from "./bookings.service";
|
||||
import type { Freight, PaginatedResponse } from "@edr/types";
|
||||
import {
|
||||
bookingsService,
|
||||
BookingListFilter,
|
||||
type ApproveStepPayload,
|
||||
type PaginatedBookings,
|
||||
type RejectStepPayload,
|
||||
} from "./bookings.service";
|
||||
import type { BookingDetail } from "@/types/booking";
|
||||
|
||||
export const api = {
|
||||
fileUploadSettings: {
|
||||
@@ -167,15 +173,21 @@ export const api = {
|
||||
list: endpoint<
|
||||
{ resource: RuleEngineResourceSlug; params?: RuleEngineListParams },
|
||||
RuleEngineListResult<RuleEngineRecord>
|
||||
>("rule-engine", "list", ({ resource, params }) =>
|
||||
ruleEngineService.list(resource, params),
|
||||
>(
|
||||
"rule-engine",
|
||||
"list",
|
||||
({ resource, params }) => ruleEngineService.list(resource, params),
|
||||
({ resource, params }) => QUERY_KEYS.RULE_ENGINE.list(resource, params),
|
||||
),
|
||||
|
||||
getById: endpoint<
|
||||
{ resource: RuleEngineResourceSlug; id: string },
|
||||
RuleEngineRecord
|
||||
>("rule-engine", "getById", ({ resource, id }) =>
|
||||
ruleEngineService.getById(resource, id),
|
||||
>(
|
||||
"rule-engine",
|
||||
"getById",
|
||||
({ resource, id }) => ruleEngineService.getById(resource, id),
|
||||
({ resource, id }) => QUERY_KEYS.RULE_ENGINE.detail(resource, id),
|
||||
),
|
||||
|
||||
create: endpoint<
|
||||
@@ -209,37 +221,33 @@ export const api = {
|
||||
({ id }) => ruleEngineService.submitRate(id),
|
||||
),
|
||||
|
||||
approveRate: endpoint<
|
||||
{ id: string; payload: ApproveRatePayload },
|
||||
RuleEngineRecord
|
||||
>("rule-engine", "approveRate", ({ id, payload }) =>
|
||||
ruleEngineService.approveRate(id, payload),
|
||||
approveRate: endpoint<{ id: string }, RuleEngineRecord>(
|
||||
"rule-engine",
|
||||
"approveRate",
|
||||
({ id }) => ruleEngineService.approveRate(id),
|
||||
),
|
||||
|
||||
getApprovalChain: endpoint<void, RuleEngineRecord[]>(
|
||||
"rule-engine",
|
||||
"getApprovalChain",
|
||||
() => ruleEngineService.getApprovalChain(),
|
||||
() => QUERY_KEYS.RULE_ENGINE.chain,
|
||||
),
|
||||
},
|
||||
|
||||
bookings: {
|
||||
list: endpoint<
|
||||
{ filter?: BookingListFilter },
|
||||
PaginatedResponse<Freight.IBooking>
|
||||
>("bookings", "list", ({ filter }) => bookingsService.list(filter)),
|
||||
list: endpoint<{ filter?: BookingListFilter }, PaginatedBookings>(
|
||||
"bookings",
|
||||
"list",
|
||||
({ filter }) => bookingsService.list(filter),
|
||||
({ filter }) => QUERY_KEYS.BOOKINGS.list(filter),
|
||||
),
|
||||
|
||||
getById: endpoint<{ id: string }, Freight.IBooking>(
|
||||
getById: endpoint<{ id: string }, BookingDetail>(
|
||||
"bookings",
|
||||
"getById",
|
||||
({ id }) => bookingsService.getById(id),
|
||||
),
|
||||
|
||||
updateStatus: endpoint<
|
||||
{ id: string; action: string; reason?: string },
|
||||
Freight.IBooking
|
||||
>("bookings", "updateStatus", ({ id, action, reason }) =>
|
||||
bookingsService.updateStatus(id, { action, reason }),
|
||||
({ id }) => QUERY_KEYS.BOOKINGS.byId(id),
|
||||
),
|
||||
|
||||
remove: endpoint<{ id: string }, void>(
|
||||
@@ -247,5 +255,84 @@ export const api = {
|
||||
"remove",
|
||||
({ id }) => bookingsService.remove(id),
|
||||
),
|
||||
|
||||
staffAccept: endpoint<{ id: string }, BookingDetail>(
|
||||
"bookings",
|
||||
"staffAccept",
|
||||
({ id }) => bookingsService.staffAccept(id),
|
||||
),
|
||||
|
||||
requestChanges: endpoint<{ id: string; note: string }, BookingDetail>(
|
||||
"bookings",
|
||||
"requestChanges",
|
||||
({ id, note }) => bookingsService.requestChanges(id, note),
|
||||
),
|
||||
|
||||
staffReject: endpoint<{ id: string; reason: string }, BookingDetail>(
|
||||
"bookings",
|
||||
"staffReject",
|
||||
({ id, reason }) => bookingsService.staffReject(id, reason),
|
||||
),
|
||||
|
||||
approveStep: endpoint<ApproveStepPayload, BookingDetail>(
|
||||
"bookings",
|
||||
"approveStep",
|
||||
(payload) => bookingsService.approveStep(payload),
|
||||
),
|
||||
|
||||
rejectStep: endpoint<RejectStepPayload, BookingDetail>(
|
||||
"bookings",
|
||||
"rejectStep",
|
||||
(payload) => bookingsService.rejectStep(payload),
|
||||
),
|
||||
|
||||
generateContract: endpoint<{ id: string }, BookingDetail>(
|
||||
"bookings",
|
||||
"generateContract",
|
||||
({ id }) => bookingsService.generateContract(id),
|
||||
),
|
||||
|
||||
getContractView: endpoint<{ id: string }, import("./bookings.service").ContractView>(
|
||||
"bookings",
|
||||
"getContractView",
|
||||
({ id }) => bookingsService.getContractView(id),
|
||||
),
|
||||
|
||||
signContract: endpoint<
|
||||
{ id: string } & import("./bookings.service").SignContractPayload,
|
||||
BookingDetail
|
||||
>("bookings", "signContract", ({ id, ...payload }) =>
|
||||
bookingsService.signContract(id, payload),
|
||||
),
|
||||
|
||||
generatePnr: endpoint<{ id: string }, BookingDetail>(
|
||||
"bookings",
|
||||
"generatePnr",
|
||||
({ id }) => bookingsService.generatePnr(id),
|
||||
),
|
||||
|
||||
verifyPayment: endpoint<{ id: string }, BookingDetail>(
|
||||
"bookings",
|
||||
"verifyPayment",
|
||||
({ id }) => bookingsService.verifyPayment(id),
|
||||
),
|
||||
|
||||
startTransit: endpoint<{ id: string }, BookingDetail>(
|
||||
"bookings",
|
||||
"startTransit",
|
||||
({ id }) => bookingsService.startTransit(id),
|
||||
),
|
||||
|
||||
complete: endpoint<{ id: string }, BookingDetail>(
|
||||
"bookings",
|
||||
"complete",
|
||||
({ id }) => bookingsService.complete(id),
|
||||
),
|
||||
|
||||
cancel: endpoint<{ id: string; reason: string }, BookingDetail>(
|
||||
"bookings",
|
||||
"cancel",
|
||||
({ id, reason }) => bookingsService.cancel(id, reason),
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,51 +1,172 @@
|
||||
import type { Freight, PaginatedResponse } from "@edr/types";
|
||||
|
||||
import { api as client } from "../auth/http";
|
||||
import { unwrap } from "@/utils/endpoint";
|
||||
import { URL_CONSTANTS } from "@/constants/URLS";
|
||||
import type { BookingDetail } from "@/types/booking";
|
||||
|
||||
const BASE = URL_CONSTANTS.BOOKINGS.BASE;
|
||||
const B = URL_CONSTANTS.BOOKINGS;
|
||||
|
||||
export interface BookingListFilter {
|
||||
status?: string;
|
||||
customerId?: string;
|
||||
search?: string;
|
||||
freightType?: string;
|
||||
tradeDirection?: string;
|
||||
paymentCurrency?: string;
|
||||
page?: number;
|
||||
pageSize?: number;
|
||||
sortBy?: string;
|
||||
sortOrder?: "ASC" | "DESC";
|
||||
}
|
||||
|
||||
export interface PaginatedBookings {
|
||||
items: BookingDetail[];
|
||||
total: number;
|
||||
}
|
||||
|
||||
export interface ApproveStepPayload {
|
||||
id: string;
|
||||
stepId: string;
|
||||
requiredRole: string;
|
||||
}
|
||||
|
||||
export interface RejectStepPayload {
|
||||
id: string;
|
||||
stepId: string;
|
||||
reason: string;
|
||||
}
|
||||
|
||||
export interface ContractView {
|
||||
bookingId: string;
|
||||
reference: string;
|
||||
status: string;
|
||||
templateKey: string;
|
||||
title: string;
|
||||
html: string;
|
||||
canSignCustomer: boolean;
|
||||
canSignStaff: boolean;
|
||||
hasContractDocument: boolean;
|
||||
signatures: Array<{
|
||||
role: string;
|
||||
signerDisplayName: string;
|
||||
signedAt: string;
|
||||
signatureImageUrl?: string | null;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface SignContractPayload {
|
||||
role: "CUSTOMER" | "STAFF";
|
||||
signatureImageBase64: string;
|
||||
signerDisplayName: string;
|
||||
consentText?: string;
|
||||
}
|
||||
|
||||
async function postBooking<T>(url: string, body?: unknown): Promise<T> {
|
||||
const response = await client.post<T>(url, body ?? {});
|
||||
return unwrap(response.data);
|
||||
}
|
||||
|
||||
export const bookingsService = {
|
||||
list: async (
|
||||
filter?: BookingListFilter,
|
||||
): Promise<PaginatedResponse<Freight.IBooking>> => {
|
||||
const response = await client.get<PaginatedResponse<Freight.IBooking>>(
|
||||
BASE,
|
||||
{ params: filter },
|
||||
);
|
||||
return unwrap(response.data);
|
||||
list: async (filter?: BookingListFilter): Promise<PaginatedBookings> => {
|
||||
const response = await client.get<PaginatedBookings>(B.BASE, {
|
||||
params: filter,
|
||||
});
|
||||
const data = unwrap(response.data);
|
||||
return {
|
||||
items: (data.items ?? []) as BookingDetail[],
|
||||
total: data.total ?? 0,
|
||||
};
|
||||
},
|
||||
|
||||
getById: async (id: string): Promise<Freight.IBooking> => {
|
||||
const response = await client.get<Freight.IBooking>(
|
||||
URL_CONSTANTS.BOOKINGS.BY_ID(id),
|
||||
);
|
||||
return unwrap(response.data);
|
||||
},
|
||||
|
||||
updateStatus: async (
|
||||
id: string,
|
||||
payload: { action: string; reason?: string },
|
||||
): Promise<Freight.IBooking> => {
|
||||
const response = await client.patch<Freight.IBooking>(
|
||||
`${URL_CONSTANTS.BOOKINGS.BY_ID(id)}/status`,
|
||||
payload,
|
||||
);
|
||||
return unwrap(response.data);
|
||||
getById: async (id: string): Promise<BookingDetail> => {
|
||||
const response = await client.get<BookingDetail>(B.BY_ID(id));
|
||||
return unwrap(response.data) as BookingDetail;
|
||||
},
|
||||
|
||||
remove: async (id: string): Promise<void> => {
|
||||
await client.delete(URL_CONSTANTS.BOOKINGS.BY_ID(id));
|
||||
await client.delete(B.BY_ID(id));
|
||||
},
|
||||
|
||||
staffAccept: (id: string) => postBooking<BookingDetail>(B.STAFF_ACCEPT(id)),
|
||||
|
||||
requestChanges: (id: string, note: string) =>
|
||||
postBooking<BookingDetail>(B.STAFF_REQUEST_CHANGES(id), { note }),
|
||||
|
||||
staffReject: (id: string, reason: string) =>
|
||||
postBooking<BookingDetail>(B.STAFF_REJECT(id), { reason }),
|
||||
|
||||
approveStep: ({ id, stepId, requiredRole }: ApproveStepPayload) =>
|
||||
postBooking<BookingDetail>(B.APPROVE_STEP(id, stepId), { requiredRole }),
|
||||
|
||||
rejectStep: ({ id, stepId, reason }: RejectStepPayload) =>
|
||||
postBooking<BookingDetail>(B.REJECT_STEP(id, stepId), { reason }),
|
||||
|
||||
generateContract: (id: string) =>
|
||||
postBooking<BookingDetail>(B.CONTRACT_GENERATE(id)),
|
||||
|
||||
getContractView: async (id: string): Promise<ContractView> => {
|
||||
const response = await client.get<ContractView>(B.CONTRACT_VIEW(id));
|
||||
return unwrap(response.data) as ContractView;
|
||||
},
|
||||
|
||||
downloadContract: async (id: string): Promise<Blob> => {
|
||||
const response = await client.get(B.CONTRACT_DOWNLOAD(id), {
|
||||
responseType: "blob",
|
||||
});
|
||||
return response.data as Blob;
|
||||
},
|
||||
|
||||
downloadContractDocument: async (id: string): Promise<Blob> => {
|
||||
const response = await client.get(B.CONTRACT_DOCUMENT(id), {
|
||||
responseType: "blob",
|
||||
});
|
||||
return response.data as Blob;
|
||||
},
|
||||
|
||||
signContract: (id: string, payload: SignContractPayload) =>
|
||||
postBooking<BookingDetail>(B.CONTRACT_SIGN(id), payload),
|
||||
|
||||
getSummary: async (id: string): Promise<{ summary: string }> => {
|
||||
const response = await client.get<{ summary: string }>(B.SUMMARY(id));
|
||||
return unwrap(response.data);
|
||||
},
|
||||
|
||||
customerSign: (id: string, payload: SignContractPayload) =>
|
||||
postBooking<BookingDetail>(B.CUSTOMER_SIGN(id), {
|
||||
...payload,
|
||||
role: "CUSTOMER",
|
||||
}),
|
||||
|
||||
marketingApprove: (id: string, payload: SignContractPayload) =>
|
||||
postBooking<BookingDetail>(B.MARKETING_APPROVE(id), {
|
||||
...payload,
|
||||
role: "STAFF",
|
||||
}),
|
||||
|
||||
generatePnr: (id: string) => postBooking<BookingDetail>(B.PAYMENT_PNR(id)),
|
||||
|
||||
submitPaymentProof: async (id: string, file: File): Promise<BookingDetail> => {
|
||||
const form = new FormData();
|
||||
form.append("file", file);
|
||||
const response = await client.post<BookingDetail>(B.PAYMENT_PROOF(id), form, {
|
||||
headers: { "Content-Type": "multipart/form-data" },
|
||||
});
|
||||
return unwrap(response.data) as BookingDetail;
|
||||
},
|
||||
|
||||
verifyPayment: (id: string) =>
|
||||
postBooking<BookingDetail>(B.PAYMENT_VERIFY(id)),
|
||||
|
||||
downloadPaymentRequestLetter: async (id: string): Promise<Blob> => {
|
||||
const response = await client.get(B.PAYMENT_REQUEST_LETTER(id), {
|
||||
responseType: "blob",
|
||||
});
|
||||
return response.data as Blob;
|
||||
},
|
||||
|
||||
startTransit: (id: string) =>
|
||||
postBooking<BookingDetail>(B.START_TRANSIT(id)),
|
||||
|
||||
complete: (id: string) => postBooking<BookingDetail>(B.COMPLETE(id)),
|
||||
|
||||
cancel: (id: string, reason: string) =>
|
||||
postBooking<BookingDetail>(B.CANCEL(id), { reason }),
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { api as client } from "@/auth/http";
|
||||
import { URL_CONSTANTS } from "@/constants/URLS";
|
||||
import type {
|
||||
ApproveRatePayload,
|
||||
RuleEngineListMeta,
|
||||
RuleEngineListResult,
|
||||
RuleEngineRecord,
|
||||
@@ -143,11 +142,8 @@ export const ruleEngineService = {
|
||||
return normalizeEntity<T>(response.data);
|
||||
},
|
||||
|
||||
approveRate: async <T extends RuleEngineRecord>(
|
||||
id: string,
|
||||
payload: ApproveRatePayload,
|
||||
): Promise<T> => {
|
||||
const response = await client.post(URL_CONSTANTS.RULE_ENGINE.RATE_APPROVE(id), payload);
|
||||
approveRate: async <T extends RuleEngineRecord>(id: string): Promise<T> => {
|
||||
const response = await client.post(URL_CONSTANTS.RULE_ENGINE.RATE_APPROVE(id));
|
||||
return normalizeEntity<T>(response.data);
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user