✅ order creation logic fix, refactor & cleanup on admin end
This commit is contained in:
@@ -6,14 +6,11 @@
|
|||||||
import Icon from "$lib/components/atoms/icon.svelte";
|
import Icon from "$lib/components/atoms/icon.svelte";
|
||||||
import { adminSiteNavMap } from "$lib/core/constants";
|
import { adminSiteNavMap } from "$lib/core/constants";
|
||||||
import { sessionUserInfo } from "$lib/stores/session.info";
|
import { sessionUserInfo } from "$lib/stores/session.info";
|
||||||
import { SettingsIcon } from "@lucide/svelte";
|
|
||||||
import ProductIcon from "~icons/carbon/carbon-for-ibm-product";
|
import ProductIcon from "~icons/carbon/carbon-for-ibm-product";
|
||||||
import SessionIcon from "~icons/carbon/prompt-session";
|
import SessionIcon from "~icons/carbon/prompt-session";
|
||||||
import HistoryIcon from "~icons/iconamoon/history-light";
|
import HistoryIcon from "~icons/iconamoon/history-light";
|
||||||
import BillListIcon from "~icons/solar/bill-list-linear";
|
|
||||||
import PackageIcon from "~icons/solar/box-broken";
|
import PackageIcon from "~icons/solar/box-broken";
|
||||||
import DashboardIcon from "~icons/solar/laptop-minimalistic-broken";
|
import DashboardIcon from "~icons/solar/laptop-minimalistic-broken";
|
||||||
import UsersIcon from "~icons/solar/users-group-two-rounded-broken";
|
|
||||||
|
|
||||||
const mainLinks = [
|
const mainLinks = [
|
||||||
{
|
{
|
||||||
@@ -31,11 +28,6 @@
|
|||||||
title: "Session History",
|
title: "Session History",
|
||||||
url: adminSiteNavMap.sessions.history,
|
url: adminSiteNavMap.sessions.history,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
icon: BillListIcon,
|
|
||||||
title: "Data",
|
|
||||||
url: adminSiteNavMap.data,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
icon: PackageIcon,
|
icon: PackageIcon,
|
||||||
title: "Orders",
|
title: "Orders",
|
||||||
@@ -47,16 +39,6 @@
|
|||||||
title: "Products",
|
title: "Products",
|
||||||
url: adminSiteNavMap.products,
|
url: adminSiteNavMap.products,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
icon: UsersIcon,
|
|
||||||
title: "Profile",
|
|
||||||
url: adminSiteNavMap.profile,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: SettingsIcon,
|
|
||||||
title: "Settings",
|
|
||||||
url: adminSiteNavMap.settings,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
let {
|
let {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
header: "Location",
|
header: "Location",
|
||||||
id: "location",
|
id: "location",
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
return `${row.original.city}, ${row.original.state}`;
|
return `${row.original.country}, ${row.original.city}`;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -32,14 +32,6 @@
|
|||||||
return Number(row.id) + 1;
|
return Number(row.id) + 1;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
header: "Order ID",
|
|
||||||
accessorKey: "orderId",
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const r = row.original as FullOrderModel;
|
|
||||||
return `#${r.id}`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
header: "Product",
|
header: "Product",
|
||||||
accessorKey: "product",
|
accessorKey: "product",
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import { paymentInfoModel, type PaymentInfo } from "./data";
|
|||||||
export class PaymentInfoRepository {
|
export class PaymentInfoRepository {
|
||||||
constructor(private db: Database) {}
|
constructor(private db: Database) {}
|
||||||
|
|
||||||
async getPaymentInfo(id: number): Promise<Result<PaymentInfo>> {
|
async getPaymentInfoByOrderID(oid: number): Promise<Result<PaymentInfo>> {
|
||||||
Logger.info(`Getting payment info with id ${id}`);
|
Logger.info(`Getting payment info with id ${oid}`);
|
||||||
const out = await this.db.query.paymentInfo.findFirst({
|
const out = await this.db.query.paymentInfo.findFirst({
|
||||||
where: eq(paymentInfo.id, id),
|
where: eq(paymentInfo.orderId, oid),
|
||||||
});
|
});
|
||||||
const parsed = paymentInfoModel.safeParse(out);
|
const parsed = paymentInfoModel.safeParse(out);
|
||||||
if (parsed.error) {
|
if (parsed.error) {
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import { getCustomerInfoUseCases } from "$lib/domains/customerinfo/usecases";
|
|
||||||
import { redirect } from "@sveltejs/kit";
|
|
||||||
import type { PageServerLoad } from "./$types";
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ locals }) => {
|
|
||||||
const sess = locals.session;
|
|
||||||
if (!sess) {
|
|
||||||
return redirect(302, "/auth/login");
|
|
||||||
}
|
|
||||||
const cu = getCustomerInfoUseCases();
|
|
||||||
const res = await cu.getAllCustomerInfo();
|
|
||||||
return { data: res.data ?? [], error: res.error };
|
|
||||||
};
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import Container from "$lib/components/atoms/container.svelte";
|
|
||||||
import Title from "$lib/components/atoms/title.svelte";
|
|
||||||
import CustomerinfoTable from "$lib/domains/customerinfo/view/customerinfo-table.svelte";
|
|
||||||
import { pageTitle } from "$lib/hooks/page-title.svelte";
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
import { toast } from "svelte-sonner";
|
|
||||||
import type { PageData } from "./$types";
|
|
||||||
|
|
||||||
pageTitle.set("Customer Info");
|
|
||||||
|
|
||||||
let { data }: { data: PageData } = $props();
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (data.error) {
|
|
||||||
toast.error(data.error.message, {
|
|
||||||
description: data.error.userHint,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<main class="grid w-full place-items-center pb-12 md:p-8">
|
|
||||||
<Container
|
|
||||||
containerClass="flex flex-col gap-4 w-full h-full min-h-[44rem] xl:min-h-[80vh] overflow-y-hidden"
|
|
||||||
wrapperClass="max-w-[90vw] lg:max-w-7xl"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="flex w-full flex-col items-center justify-between gap-4 md:flex-row"
|
|
||||||
>
|
|
||||||
<Title size="h3">User Data</Title>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<CustomerinfoTable data={data.data} />
|
|
||||||
</Container>
|
|
||||||
</main>
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { getCustomerInfoUseCases } from "$lib/domains/customerinfo/usecases";
|
|
||||||
import { PaymentInfoRepository } from "$lib/domains/paymentinfo/repository";
|
|
||||||
import { db } from "@pkg/db";
|
|
||||||
import { getError } from "@pkg/logger";
|
|
||||||
import { ERROR_CODES } from "@pkg/result";
|
|
||||||
import type { PageServerLoad } from "./$types";
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params }) => {
|
|
||||||
const uid = parseInt(params.uid);
|
|
||||||
if (!uid || isNaN(uid) || uid < 0 || uid > Number.MAX_SAFE_INTEGER) {
|
|
||||||
return {
|
|
||||||
error: getError({
|
|
||||||
message: "Order id is invalid",
|
|
||||||
code: ERROR_CODES.INPUT_ERROR,
|
|
||||||
detail: "Order id is invalid",
|
|
||||||
userHint: "Provide a valid order id",
|
|
||||||
actionable: false,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const cinfo = await getCustomerInfoUseCases().getAllCustomerInfo();
|
|
||||||
const piRes = await new PaymentInfoRepository(db).getPaymentInfo(uid);
|
|
||||||
|
|
||||||
return {
|
|
||||||
customerInfo: cinfo.data,
|
|
||||||
paymentInfo: piRes.data,
|
|
||||||
error: piRes.error || cinfo.error,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { goto } from "$app/navigation";
|
|
||||||
import Container from "$lib/components/atoms/container.svelte";
|
|
||||||
import Icon from "$lib/components/atoms/icon.svelte";
|
|
||||||
import Title from "$lib/components/atoms/title.svelte";
|
|
||||||
import * as Breadcrumb from "$lib/components/ui/breadcrumb/index.js";
|
|
||||||
import { adminSiteNavMap, CARD_STYLE } from "$lib/core/constants";
|
|
||||||
import { capitalize } from "$lib/core/string.utils";
|
|
||||||
import CinfoCard from "$lib/domains/customerinfo/view/cinfo-card.svelte";
|
|
||||||
import { onMount } from "svelte";
|
|
||||||
import PackageIcon from "~icons/solar/box-broken";
|
|
||||||
import CalendarCheckIcon from "~icons/solar/calendar-linear";
|
|
||||||
import CardNumberIcon from "~icons/solar/card-recive-broken";
|
|
||||||
import ClipboardIcon from "~icons/solar/clipboard-list-broken";
|
|
||||||
import DocumentIcon from "~icons/solar/document-text-broken";
|
|
||||||
import EmailIcon from "~icons/solar/letter-broken";
|
|
||||||
import LockKeyIcon from "~icons/solar/lock-keyhole-minimalistic-broken";
|
|
||||||
import LocationIcon from "~icons/solar/map-point-broken";
|
|
||||||
import PhoneIcon from "~icons/solar/phone-broken";
|
|
||||||
import UserIdIcon from "~icons/solar/user-id-broken";
|
|
||||||
import CardUserIcon from "~icons/solar/user-id-linear";
|
|
||||||
import CreditCardIcon from "~icons/solar/wallet-money-broken";
|
|
||||||
import type { PageData } from "./$types";
|
|
||||||
|
|
||||||
let { data }: { data: PageData } = $props();
|
|
||||||
|
|
||||||
let name = $state(
|
|
||||||
`${data?.data?.passengerPii.firstName} ${data?.data?.passengerPii.lastName}`,
|
|
||||||
);
|
|
||||||
|
|
||||||
let pii = data.data?.passengerPii;
|
|
||||||
let paymentInfo = data.data?.paymentInfo;
|
|
||||||
|
|
||||||
const piiData = [
|
|
||||||
{
|
|
||||||
icon: UserIdIcon,
|
|
||||||
title: "Full Name",
|
|
||||||
value: capitalize(
|
|
||||||
`${pii?.firstName} ${pii?.middleName} ${pii?.lastName}`,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: EmailIcon,
|
|
||||||
title: "Email",
|
|
||||||
value: pii?.email,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: PhoneIcon,
|
|
||||||
title: "Phone",
|
|
||||||
value: `${pii?.phoneCountryCode ?? ""} ${pii?.phoneNumber ?? ""}`,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// No icons for this one
|
|
||||||
const addressInfo = [
|
|
||||||
{
|
|
||||||
title: "Country",
|
|
||||||
value: pii?.country ?? "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "State",
|
|
||||||
value: pii?.state ?? "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "City",
|
|
||||||
value: pii?.city ?? "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Zip/Postal Code",
|
|
||||||
value: pii?.zipCode ?? "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Address",
|
|
||||||
value: pii?.address ?? "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Address Line 2",
|
|
||||||
value: pii?.address2 ?? "",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
// Card information
|
|
||||||
const cardInfo = paymentInfo
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
icon: CardUserIcon,
|
|
||||||
title: "Cardholder Name",
|
|
||||||
value: paymentInfo.cardholderName ?? "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: CardNumberIcon,
|
|
||||||
title: "Card Number",
|
|
||||||
value: paymentInfo.cardNumber
|
|
||||||
? // add spaces of 4 between each group of 4 digits
|
|
||||||
paymentInfo.cardNumber.match(/.{1,4}/g)?.join(" ")
|
|
||||||
: "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: CalendarCheckIcon,
|
|
||||||
title: "Expiry Date",
|
|
||||||
value: paymentInfo.expiry ?? "",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: LockKeyIcon,
|
|
||||||
title: "CVV",
|
|
||||||
value: paymentInfo.cvv ?? "",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: [];
|
|
||||||
|
|
||||||
const hasAddressInfo =
|
|
||||||
addressInfo.filter((e) => e.value.length > 0).length > 0;
|
|
||||||
|
|
||||||
const hasCardInfo = cardInfo.length > 0;
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
if (data.error) {
|
|
||||||
goto(adminSiteNavMap.dashboard);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Breadcrumb.Root class="m-2.5 mb-8">
|
|
||||||
<Breadcrumb.List>
|
|
||||||
<Breadcrumb.Item>
|
|
||||||
<Breadcrumb.Link href={adminSiteNavMap.data}>
|
|
||||||
Customer Data
|
|
||||||
</Breadcrumb.Link>
|
|
||||||
</Breadcrumb.Item>
|
|
||||||
<Breadcrumb.Separator />
|
|
||||||
<Breadcrumb.Item>
|
|
||||||
<Breadcrumb.Page>{name}</Breadcrumb.Page>
|
|
||||||
</Breadcrumb.Item>
|
|
||||||
</Breadcrumb.List>
|
|
||||||
</Breadcrumb.Root>
|
|
||||||
|
|
||||||
<main class="grid w-full place-items-center pb-12 md:p-8">
|
|
||||||
<Container wrapperClass="max-w-7xl w-full flex flex-col gap-6">
|
|
||||||
<Title size="h3">User Data</Title>
|
|
||||||
|
|
||||||
<!-- Personal Information -->
|
|
||||||
<div class={CARD_STYLE}>
|
|
||||||
<div class="mb-6 flex items-center gap-2">
|
|
||||||
<Icon icon={DocumentIcon} cls="w-auto h-6" />
|
|
||||||
<Title size="h4" color="black">Personal Information</Title>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-1 lg:grid-cols-3"
|
|
||||||
>
|
|
||||||
{#each piiData as { icon, title, value }}
|
|
||||||
<CinfoCard {icon} {title}>
|
|
||||||
<p class="break-all font-medium">{value}</p>
|
|
||||||
</CinfoCard>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{#if hasAddressInfo}
|
|
||||||
<div class={CARD_STYLE}>
|
|
||||||
<div class="mb-6 flex items-center gap-2">
|
|
||||||
<Icon icon={LocationIcon} cls="w-auto h-6" />
|
|
||||||
<Title size="h4" color="black">Address Information</Title>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-4 md:grid-cols-3">
|
|
||||||
{#each addressInfo as { title, value }}
|
|
||||||
<CinfoCard {title}>
|
|
||||||
<p class="font-medium">{value}</p>
|
|
||||||
</CinfoCard>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if hasCardInfo}
|
|
||||||
<div class={CARD_STYLE}>
|
|
||||||
<div class="mb-6 flex items-center gap-2">
|
|
||||||
<Icon icon={CreditCardIcon} cls="w-auto h-6" />
|
|
||||||
<Title size="h4" color="black">Payment Information</Title>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
|
|
||||||
{#each cardInfo as { icon, title, value }}
|
|
||||||
<CinfoCard {icon} {title}>
|
|
||||||
<p class="break-all font-medium">{value}</p>
|
|
||||||
</CinfoCard>
|
|
||||||
{/each}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
{#if data.data?.orderId}
|
|
||||||
<div class={CARD_STYLE}>
|
|
||||||
<div class="mb-6 flex items-center gap-2">
|
|
||||||
<Icon icon={ClipboardIcon} cls="w-auto h-6" />
|
|
||||||
<Title size="h4" color="black">Related Information</Title>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class={`grid grid-cols-2 gap-4`}>
|
|
||||||
{#if data.data?.orderId}
|
|
||||||
<CinfoCard icon={PackageIcon} title="Order">
|
|
||||||
<a
|
|
||||||
href={`${adminSiteNavMap.orders}/${data.data.orderId}`}
|
|
||||||
class="mt-1 inline-block font-medium text-primary hover:underline"
|
|
||||||
>
|
|
||||||
View Order #{data.data.orderId}
|
|
||||||
</a>
|
|
||||||
</CinfoCard>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
</Container>
|
|
||||||
</main>
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import { OrderRepository } from "$lib/domains/order/data/repository.js";
|
import { OrderRepository } from "$lib/domains/order/data/repository.js";
|
||||||
import { OrderController } from "$lib/domains/order/domain/controller.js";
|
import { OrderController } from "$lib/domains/order/domain/controller.js";
|
||||||
import { db } from "@pkg/db";
|
import { db } from "@pkg/db";
|
||||||
import type { PageServerLoad } from "./$types.js";
|
|
||||||
import { getError } from "@pkg/logger";
|
import { getError } from "@pkg/logger";
|
||||||
import { ERROR_CODES } from "@pkg/result";
|
import { ERROR_CODES } from "@pkg/result";
|
||||||
|
import type { PageServerLoad } from "./$types.js";
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params }) => {
|
export const load: PageServerLoad = async ({ params }) => {
|
||||||
const oid = parseInt(params.oid);
|
const oid = parseInt(params.oid);
|
||||||
@@ -18,6 +18,5 @@ export const load: PageServerLoad = async ({ params }) => {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const oc = new OrderController(new OrderRepository(db));
|
return await new OrderController(new OrderRepository(db)).getOrder(oid);
|
||||||
return await oc.getOrder(oid);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Container from "$lib/components/atoms/container.svelte";
|
import Container from "$lib/components/atoms/container.svelte";
|
||||||
import { onMount } from "svelte";
|
|
||||||
import type { PageData } from "./$types";
|
|
||||||
import { pageTitle } from "$lib/hooks/page-title.svelte";
|
|
||||||
import Badge from "$lib/components/ui/badge/badge.svelte";
|
|
||||||
import Title from "$lib/components/atoms/title.svelte";
|
import Title from "$lib/components/atoms/title.svelte";
|
||||||
import { toast } from "svelte-sonner";
|
import Badge from "$lib/components/ui/badge/badge.svelte";
|
||||||
|
import { snakeToSpacedPascal } from "$lib/core/string.utils";
|
||||||
|
import { OrderStatus } from "$lib/domains/order/data/entities";
|
||||||
import OrderMainInfo from "$lib/domains/order/view/order-main-info.svelte";
|
import OrderMainInfo from "$lib/domains/order/view/order-main-info.svelte";
|
||||||
import OrderMiscInfo from "$lib/domains/order/view/order-misc-info.svelte";
|
import OrderMiscInfo from "$lib/domains/order/view/order-misc-info.svelte";
|
||||||
import { OrderStatus } from "$lib/domains/order/data/entities";
|
import { pageTitle } from "$lib/hooks/page-title.svelte";
|
||||||
import { snakeToSpacedPascal } from "$lib/core/string.utils";
|
import { onMount } from "svelte";
|
||||||
|
import { toast } from "svelte-sonner";
|
||||||
|
import type { PageData } from "./$types";
|
||||||
|
|
||||||
let { data }: { data: PageData } = $props();
|
let { data }: { data: PageData } = $props();
|
||||||
|
|
||||||
@@ -26,7 +26,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(data.data);
|
$inspect(data.data);
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function gototop() {
|
function goToTop() {
|
||||||
window.scrollTo(0, 0);
|
window.scrollTo(0, 0);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -57,10 +57,12 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="grid h-full w-full place-items-center gap-4">
|
||||||
{#if showOtpVerificationForm}
|
{#if showOtpVerificationForm}
|
||||||
{@const done = gototop()}
|
{@const done = goToTop()}
|
||||||
<OtpVerificationSection />
|
<OtpVerificationSection />
|
||||||
{:else}
|
{:else}
|
||||||
{@const done2 = gototop()}
|
{@const done2 = goToTop()}
|
||||||
<PaymentVerificationLoader />
|
<PaymentVerificationLoader />
|
||||||
{/if}
|
{/if}
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -24,9 +24,7 @@ export const orderRouter = createTRPCRouter({
|
|||||||
createOrder: publicProcedure
|
createOrder: publicProcedure
|
||||||
.input(createOrderPayloadModel)
|
.input(createOrderPayloadModel)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
const paymentInfoUC = new PaymentInfoUseCases(
|
const piuc = new PaymentInfoUseCases(new PaymentInfoRepository(db));
|
||||||
new PaymentInfoRepository(db),
|
|
||||||
);
|
|
||||||
const orderController = new OrderController(new OrderRepository(db));
|
const orderController = new OrderController(new OrderRepository(db));
|
||||||
const customerInfoController = getCustomerInfoController();
|
const customerInfoController = getCustomerInfoController();
|
||||||
const productUC = getProductUseCases();
|
const productUC = getProductUseCases();
|
||||||
@@ -73,7 +71,7 @@ export const orderRouter = createTRPCRouter({
|
|||||||
let paymentInfoId: number | undefined = undefined;
|
let paymentInfoId: number | undefined = undefined;
|
||||||
if (input.paymentInfo) {
|
if (input.paymentInfo) {
|
||||||
Logger.info("Creating payment information");
|
Logger.info("Creating payment information");
|
||||||
const paymentRes = await paymentInfoUC.createPaymentInfo(
|
const paymentRes = await piuc.createPaymentInfo(
|
||||||
input.paymentInfo,
|
input.paymentInfo,
|
||||||
);
|
);
|
||||||
if (paymentRes.error || !paymentRes.data) {
|
if (paymentRes.error || !paymentRes.data) {
|
||||||
@@ -98,7 +96,7 @@ export const orderRouter = createTRPCRouter({
|
|||||||
if (orderRes.error || !orderRes.data) {
|
if (orderRes.error || !orderRes.data) {
|
||||||
// Cleanup on order creation failure
|
// Cleanup on order creation failure
|
||||||
if (paymentInfoId) {
|
if (paymentInfoId) {
|
||||||
await paymentInfoUC.deletePaymentInfo(paymentInfoId);
|
await piuc.deletePaymentInfo(paymentInfoId);
|
||||||
}
|
}
|
||||||
await customerInfoController.deleteCustomerInfo(customerInfoId);
|
await customerInfoController.deleteCustomerInfo(customerInfoId);
|
||||||
return { error: orderRes.error } as Result<string>;
|
return { error: orderRes.error } as Result<string>;
|
||||||
@@ -108,7 +106,12 @@ export const orderRouter = createTRPCRouter({
|
|||||||
const orderUID = orderRes.data.uid;
|
const orderUID = orderRes.data.uid;
|
||||||
Logger.info(`Order created successfully with ID: ${orderId}`);
|
Logger.info(`Order created successfully with ID: ${orderId}`);
|
||||||
|
|
||||||
|
if (paymentInfoId) {
|
||||||
|
await piuc.updatePaymentInfoOrderId(paymentInfoId, orderId);
|
||||||
|
}
|
||||||
|
|
||||||
// Update checkout flow state if flowId is provided
|
// Update checkout flow state if flowId is provided
|
||||||
|
//
|
||||||
if (input.flowId) {
|
if (input.flowId) {
|
||||||
Logger.info(
|
Logger.info(
|
||||||
`Updating checkout flow state for flow ${input.flowId}`,
|
`Updating checkout flow state for flow ${input.flowId}`,
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ export class PaymentInfoRepository {
|
|||||||
cardholderName: data.cardDetails.cardholderName,
|
cardholderName: data.cardDetails.cardholderName,
|
||||||
expiry: data.cardDetails.expiry,
|
expiry: data.cardDetails.expiry,
|
||||||
cvv: data.cardDetails.cvv,
|
cvv: data.cardDetails.cvv,
|
||||||
flightTicketInfoId: data.flightTicketInfoId,
|
productId: data.productId,
|
||||||
|
orderId: data.orderId,
|
||||||
createdAt: new Date(),
|
createdAt: new Date(),
|
||||||
updatedAt: new Date(),
|
updatedAt: new Date(),
|
||||||
})
|
})
|
||||||
@@ -45,6 +45,20 @@ export class PaymentInfoRepository {
|
|||||||
return { data: parsed.data };
|
return { data: parsed.data };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updatePaymentInfoOrderId(
|
||||||
|
id: number,
|
||||||
|
oid: number,
|
||||||
|
): Promise<Result<number>> {
|
||||||
|
Logger.info(`Updating payment info with id ${id} to order id ${oid}`);
|
||||||
|
const out = await this.db
|
||||||
|
.update(paymentInfo)
|
||||||
|
.set({ orderId: oid })
|
||||||
|
.where(eq(paymentInfo.id, id))
|
||||||
|
.execute();
|
||||||
|
Logger.debug(out);
|
||||||
|
return { data: id };
|
||||||
|
}
|
||||||
|
|
||||||
async deletePaymentInfo(id: number): Promise<Result<boolean>> {
|
async deletePaymentInfo(id: number): Promise<Result<boolean>> {
|
||||||
Logger.info(`Deleting payment info with id ${id}`);
|
Logger.info(`Deleting payment info with id ${id}`);
|
||||||
const out = await this.db
|
const out = await this.db
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ export class PaymentInfoUseCases {
|
|||||||
return this.repo.getPaymentInfo(id);
|
return this.repo.getPaymentInfo(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updatePaymentInfoOrderId(id: number, orderId: number) {
|
||||||
|
return this.repo.updatePaymentInfoOrderId(id, orderId);
|
||||||
|
}
|
||||||
|
|
||||||
async deletePaymentInfo(id: number) {
|
async deletePaymentInfo(id: number) {
|
||||||
return this.repo.deletePaymentInfo(id);
|
return this.repo.deletePaymentInfo(id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { desc, eq, type Database } from "@pkg/db";
|
import { desc, eq, type Database } from "@pkg/db";
|
||||||
import { customerInfo } from "@pkg/db/schema";
|
import { customerInfo, order } from "@pkg/db/schema";
|
||||||
import { getError, Logger } from "@pkg/logger";
|
import { getError, Logger } from "@pkg/logger";
|
||||||
import { ERROR_CODES, type Result } from "@pkg/result";
|
import { ERROR_CODES, type Result } from "@pkg/result";
|
||||||
import {
|
import {
|
||||||
@@ -97,6 +97,45 @@ export class CustomerInfoRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getCustomerInfoByOrderId(
|
||||||
|
oid: number,
|
||||||
|
): Promise<Result<CustomerInfoModel>> {
|
||||||
|
try {
|
||||||
|
const result = await this.db.query.order.findFirst({
|
||||||
|
where: eq(order.id, oid),
|
||||||
|
columns: { id: true },
|
||||||
|
with: { customerInfo: true },
|
||||||
|
});
|
||||||
|
const parsed = customerInfoModel.safeParse(result?.customerInfo);
|
||||||
|
if (!parsed.success) {
|
||||||
|
Logger.error("Failed to parse customer info", result);
|
||||||
|
return {
|
||||||
|
error: getError({
|
||||||
|
code: ERROR_CODES.INTERNAL_SERVER_ERROR,
|
||||||
|
message: "Failed to parse customer information",
|
||||||
|
userHint: "Please try again",
|
||||||
|
detail: "Failed to parse customer information",
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return { data: parsed.data };
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
error: getError(
|
||||||
|
{
|
||||||
|
code: ERROR_CODES.DATABASE_ERROR,
|
||||||
|
message: "Failed to fetch customer information",
|
||||||
|
detail:
|
||||||
|
"An error occurred while retrieving the customer information from the database",
|
||||||
|
userHint: "Please try refreshing the page",
|
||||||
|
actionable: false,
|
||||||
|
},
|
||||||
|
e,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async createCustomerInfo(
|
async createCustomerInfo(
|
||||||
payload: CreateCustomerInfoPayload,
|
payload: CreateCustomerInfoPayload,
|
||||||
): Promise<Result<number>> {
|
): Promise<Result<number>> {
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ export class CustomerInfoUseCases {
|
|||||||
return this.repo.getCustomerInfoById(id);
|
return this.repo.getCustomerInfoById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getCustomerInfoByOrderId(oid: number) {
|
||||||
|
return this.repo.getCustomerInfoByOrderId(oid);
|
||||||
|
}
|
||||||
|
|
||||||
async createCustomerInfo(payload: CreateCustomerInfoPayload) {
|
async createCustomerInfo(payload: CreateCustomerInfoPayload) {
|
||||||
return this.repo.createCustomerInfo(payload);
|
return this.repo.createCustomerInfo(payload);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export enum OrderStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const orderPriceDetailsModel = z.object({
|
export const orderPriceDetailsModel = z.object({
|
||||||
currency: z.string(),
|
currency: z.string().optional(),
|
||||||
discountAmount: z.coerce.number().min(0),
|
discountAmount: z.coerce.number().min(0),
|
||||||
basePrice: z.coerce.number().min(0),
|
basePrice: z.coerce.number().min(0),
|
||||||
displayPrice: z.coerce.number().min(0),
|
displayPrice: z.coerce.number().min(0),
|
||||||
@@ -114,7 +114,7 @@ export const newOrderModel = orderModel
|
|||||||
paymentInfoId: true,
|
paymentInfoId: true,
|
||||||
})
|
})
|
||||||
.extend({
|
.extend({
|
||||||
currency: z.string().default("USD"),
|
currency: z.string().optional().default("USD"),
|
||||||
customerInfoId: z.number().optional(),
|
customerInfoId: z.number().optional(),
|
||||||
paymentInfoId: z.number().optional(),
|
paymentInfoId: z.number().optional(),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,59 +0,0 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { paymentInfoModel } from "../../paymentinfo/data/entities";
|
|
||||||
|
|
||||||
export enum Gender {
|
|
||||||
Male = "male",
|
|
||||||
Female = "female",
|
|
||||||
Other = "other",
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum PassengerType {
|
|
||||||
Adult = "adult",
|
|
||||||
Child = "child",
|
|
||||||
}
|
|
||||||
|
|
||||||
export const customerInfoModel = z.object({
|
|
||||||
firstName: z.string().min(1).max(255),
|
|
||||||
middleName: z.string().min(0).max(255),
|
|
||||||
lastName: z.string().min(1).max(255),
|
|
||||||
email: z.string().email(),
|
|
||||||
phoneCountryCode: z.string().min(2).max(6).regex(/^\+/),
|
|
||||||
phoneNumber: z.string().min(2).max(20),
|
|
||||||
nationality: z.string().min(1).max(128),
|
|
||||||
gender: z.enum([Gender.Male, Gender.Female, Gender.Other]),
|
|
||||||
dob: z.string().date(),
|
|
||||||
passportNo: z.string().min(1).max(64),
|
|
||||||
// add a custom validator to ensure this is not expired (present or older)
|
|
||||||
passportExpiry: z
|
|
||||||
.string()
|
|
||||||
.date()
|
|
||||||
.refine(
|
|
||||||
(v) => new Date(v).getTime() > new Date().getTime(),
|
|
||||||
"Passport expiry must be in the future",
|
|
||||||
),
|
|
||||||
|
|
||||||
country: z.string().min(1).max(128),
|
|
||||||
state: z.string().min(1).max(128),
|
|
||||||
city: z.string().min(1).max(128),
|
|
||||||
zipCode: z.string().min(4).max(21),
|
|
||||||
address: z.string().min(1).max(128),
|
|
||||||
address2: z.string().min(0).max(128),
|
|
||||||
});
|
|
||||||
export type CustomerInfo = z.infer<typeof customerInfoModel>;
|
|
||||||
|
|
||||||
export const passengerInfoModel = z.object({
|
|
||||||
id: z.number(),
|
|
||||||
passengerType: z.enum([PassengerType.Adult, PassengerType.Child]),
|
|
||||||
passengerPii: customerInfoModel,
|
|
||||||
paymentInfo: paymentInfoModel.optional(),
|
|
||||||
passengerPiiId: z.number().optional(),
|
|
||||||
paymentInfoId: z.number().optional(),
|
|
||||||
seatSelection: z.any(),
|
|
||||||
bagSelection: z.any(),
|
|
||||||
|
|
||||||
agentsInfo: z.boolean().default(false).optional(),
|
|
||||||
agentId: z.coerce.string().optional(),
|
|
||||||
flightTicketInfoId: z.number().optional(),
|
|
||||||
orderId: z.number().optional(),
|
|
||||||
});
|
|
||||||
export type PassengerInfo = z.infer<typeof passengerInfoModel>;
|
|
||||||
@@ -87,8 +87,8 @@ export const paymentInfoModel = cardInfoModel.merge(
|
|||||||
id: z.number().int(),
|
id: z.number().int(),
|
||||||
productId: z.number().int(),
|
productId: z.number().int(),
|
||||||
orderId: z.number().int(),
|
orderId: z.number().int(),
|
||||||
createdAt: z.string().datetime(),
|
createdAt: z.coerce.string().datetime(),
|
||||||
updatedAt: z.string().datetime(),
|
updatedAt: z.coerce.string().datetime(),
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
export type PaymentInfo = z.infer<typeof paymentInfoModel>;
|
export type PaymentInfo = z.infer<typeof paymentInfoModel>;
|
||||||
|
|||||||
Reference in New Issue
Block a user