🔄 cleanup: order with 3 key relations, and ckflow now upto speeded

This commit is contained in:
user
2025-10-20 22:27:06 +03:00
parent 2ed74c267d
commit 10bcbf982a
28 changed files with 190 additions and 1360 deletions

View File

@@ -1,3 +1,6 @@
import { and, desc, eq, type Database } from "@pkg/db";
import { checkoutFlowSession, product } from "@pkg/db/schema";
import { getError, Logger } from "@pkg/logger";
import { ERROR_CODES, type Result } from "@pkg/result";
import {
CKActionType,
@@ -5,14 +8,11 @@ import {
type FlowInfo,
type PendingAction,
} from "./data";
import { getError, Logger } from "@pkg/logger";
import { and, desc, eq, type Database } from "@pkg/db";
import { checkoutFlowSession, flightTicketInfo } from "@pkg/db/schema";
export class CheckoutFlowRepository {
constructor(private db: Database) {}
// Common method to parse and enrich flow info with ticket details
// Common method to parse and enrich flow info with product details
private async parseFlowInfo(
sessionData: any,
includeStaleness = true,
@@ -43,48 +43,30 @@ export class CheckoutFlowRepository {
: false;
}
// Fetch ticket info if we have a ticket ID
let ticketInfo = undefined;
if (sessionData.ticketId) {
// Fetch product info if we have a product ID
let productInfo = undefined;
if (sessionData.productId) {
try {
const ticketResult =
await this.db.query.flightTicketInfo.findFirst({
where: eq(flightTicketInfo.id, sessionData.ticketId),
});
if (ticketResult) {
ticketInfo = {
id: ticketResult.id,
ticketId: ticketResult.ticketId,
departure: ticketResult.departure,
arrival: ticketResult.arrival,
departureDate: ticketResult.departureDate
.toISOString()
.split("T")[0],
returnDate: ticketResult.returnDate
? ticketResult.returnDate
.toISOString()
.split("T")[0]
: undefined,
flightType: ticketResult.flightType,
cabinClass: ticketResult.cabinClass,
priceDetails: ticketResult.priceDetails as any,
};
const prodResult = await this.db.query.product.findFirst({
where: eq(product.id, sessionData.productId),
});
if (prodResult) {
productInfo = { ...prodResult };
}
} catch (err) {
Logger.warn(
"Failed to fetch ticket info for session detail",
"Failed to fetch product info for session detail",
err,
);
// Continue without ticket info - it's optional
// Continue without product info - it's optional
}
}
// Prepare dates for the model
const flowInfoData = {
...sessionData,
ticketInfo,
ticketId: sessionData.ticketId,
productInfo: productInfo,
productId: sessionData.productId,
pendingActions: sessionData.pendingActions as any,
personalInfo: sessionData.personalInfo as any,
paymentInfo: sessionData.paymentInfo as any,

View File

@@ -1,18 +1,8 @@
<script lang="ts">
import { formatDistanceToNow } from "date-fns";
import { Badge } from "$lib/components/ui/badge";
import { ScrollArea } from "$lib/components/ui/scroll-area";
import type { FlowInfo } from "@pkg/logic/domains/ckflow/data/entities";
import Icon from "$lib/components/atoms/icon.svelte";
import { CheckoutStep } from "$lib/domains/ticket/data/entities";
import LoaderIcon from "~icons/bx/loader-alt";
import UserIcon from "~icons/material-symbols/account-circle-full";
import CreditCardIcon from "~icons/solar/card-2-linear";
import QuestionMarkIcon from "~icons/solar/question-circle-linear";
import CheckCircleIcon from "~icons/solar/check-circle-linear";
import StopCircleIcon from "~icons/solar/stop-circle-linear";
import ShieldAlertIcon from "~icons/solar/shield-warning-linear";
import TrashIcon from "~icons/solar/trash-bin-trash-linear";
import { Badge } from "$lib/components/ui/badge";
import { Button } from "$lib/components/ui/button";
import * as ContextMenu from "$lib/components/ui/context-menu/index";
import {
Dialog,
DialogContent,
@@ -21,11 +11,21 @@
DialogHeader,
DialogTitle,
} from "$lib/components/ui/dialog";
import { Button } from "$lib/components/ui/button";
import { isSessionActive } from "../utils";
import { ScrollArea } from "$lib/components/ui/scroll-area";
import { CheckoutStep } from "$lib/domains/order/data/entities";
import { trpcApiStore } from "$lib/stores/api";
import type { FlowInfo } from "@pkg/logic/domains/ckflow/data/entities";
import { formatDistanceToNow } from "date-fns";
import { toast } from "svelte-sonner";
import * as ContextMenu from "$lib/components/ui/context-menu/index";
import LoaderIcon from "~icons/bx/loader-alt";
import UserIcon from "~icons/material-symbols/account-circle-full";
import CreditCardIcon from "~icons/solar/card-2-linear";
import CheckCircleIcon from "~icons/solar/check-circle-linear";
import QuestionMarkIcon from "~icons/solar/question-circle-linear";
import ShieldAlertIcon from "~icons/solar/shield-warning-linear";
import StopCircleIcon from "~icons/solar/stop-circle-linear";
import TrashIcon from "~icons/solar/trash-bin-trash-linear";
import { isSessionActive } from "../utils";
let {
sessions,

View File

@@ -8,11 +8,13 @@
DialogHeader,
DialogTitle,
} from "$lib/components/ui/dialog";
import { PaymentErrorType } from "@pkg/logic/domains/ckflow/data/entities";
import { RadioGroup, RadioGroupItem } from "$lib/components/ui/radio-group";
import { Label } from "$lib/components/ui/label";
import { RadioGroup, RadioGroupItem } from "$lib/components/ui/radio-group";
import { Textarea } from "$lib/components/ui/textarea";
import { CKActionType } from "@pkg/logic/domains/ckflow/data/entities";
import {
CKActionType,
PaymentErrorType,
} from "@pkg/logic/domains/ckflow/data/entities";
let {
open = $bindable<boolean>(false),

View File

@@ -1,5 +1,7 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import Icon from "$lib/components/atoms/icon.svelte";
import { Alert, AlertDescription } from "$lib/components/ui/alert";
import { Badge } from "$lib/components/ui/badge";
import { Button } from "$lib/components/ui/button";
import {
Card,
@@ -9,25 +11,21 @@
CardHeader,
CardTitle,
} from "$lib/components/ui/card";
import { Badge } from "$lib/components/ui/badge";
import { Alert, AlertDescription } from "$lib/components/ui/alert";
import { Separator } from "$lib/components/ui/separator";
import { CheckoutStep } from "@pkg/logic/domains/ticket/data/entities";
import { trpcApiStore } from "$lib/stores/api";
import {
CKActionType,
type FlowInfo,
} from "@pkg/logic/domains/ckflow/data/entities";
import { CheckoutStep } from "@pkg/logic/domains/order/data/enums";
import { formatDistanceToNow } from "date-fns";
import { trpcApiStore } from "$lib/stores/api";
import { createEventDispatcher } from "svelte";
import { toast } from "svelte-sonner";
import Icon from "$lib/components/atoms/icon.svelte";
import XIcon from "~icons/material-symbols/close-rounded";
import ArrowLeftIcon from "~icons/solar/arrow-left-outline";
import CheckCircleIcon from "~icons/solar/check-circle-broken";
import XIcon from "~icons/material-symbols/close-rounded";
import TrashIcon from "~icons/solar/trash-bin-trash-linear"; // Add trash icon
import ShieldIcon from "~icons/solar/shield-keyhole-minimalistic-broken";
import { ckflowVM } from "../ckflow.vm.svelte";
import { isSessionActive } from "../utils";
import TrashIcon from "~icons/solar/trash-bin-trash-linear";
// Add trash icon
import {
Dialog,
DialogContent,
@@ -40,7 +38,9 @@
capitalize,
snakeToSpacedPascal,
} from "@pkg/logic/core/string.utils";
import { formatCurrency } from "@pkg/logic/core/currency.utils";
import ShieldIcon from "~icons/solar/shield-keyhole-minimalistic-broken";
import { ckflowVM } from "../ckflow.vm.svelte";
import { isSessionActive } from "../utils";
import BackToPaymentDialog from "./back-to-payment-dialog.svelte";
let { session = $bindable() }: { session: FlowInfo } = $props();
@@ -251,61 +251,11 @@
<Separator />
{#if session.ticketInfo}
{#if session.productInfo}
<div class="space-y-2">
<h3 class="text-lg font-medium">Booking Details</h3>
<h3 class="text-lg font-medium">Product Details</h3>
<div class="rounded-md border p-3">
<div class="mb-2 flex items-center justify-between">
<div class="font-medium">
{session.ticketInfo.departure}{session
.ticketInfo.arrival}
</div>
<Badge variant="outline">
{session.ticketInfo.flightType === "ONEWAY"
? "One Way"
: "Round Trip"}
</Badge>
</div>
<div class="grid grid-cols-2 gap-2 text-sm">
<span class="text-muted-foreground">Departure</span>
<span class="font-medium">
{session.ticketInfo.departureDate}
</span>
{#if session.ticketInfo.flightType !== "ONEWAY" && session.ticketInfo.returnDate}
<span class="text-muted-foreground">Return</span>
<span class="font-medium">
{session.ticketInfo.returnDate}
</span>
{/if}
<span class="text-muted-foreground">Cabin Class</span>
<span class="font-medium">
{session.ticketInfo.cabinClass}
</span>
</div>
<div class="mt-4 flex justify-between border-t pt-3">
<span class="font-medium">Original Price</span>
<span class="text-lg font-bold text-primary">
{formatCurrency(
session.ticketInfo.priceDetails.basePrice ??
0,
session.ticketInfo.priceDetails.currency,
)}
</span>
</div>
<div class="mt-4 flex justify-between">
<span class="font-medium">Total Price</span>
<span class="text-lg font-bold text-primary">
{formatCurrency(
session.ticketInfo.priceDetails.displayPrice,
session.ticketInfo.priceDetails.currency,
)}
</span>
</div>
<span>TODO: show product details here</span>
</div>
</div>

View File

@@ -1,18 +1,14 @@
<script lang="ts">
import Icon from "$lib/components/atoms/icon.svelte";
import { Badge } from "$lib/components/ui/badge";
import { Button } from "$lib/components/ui/button";
import type { FlowInfo } from "@pkg/logic/domains/ckflow/data/entities";
import { Separator } from "$lib/components/ui/separator";
import {
capitalize,
snakeToSpacedPascal,
} from "@pkg/logic/core/string.utils";
import { Separator } from "$lib/components/ui/separator";
import Icon from "$lib/components/atoms/icon.svelte";
import type { FlowInfo } from "@pkg/logic/domains/ckflow/data/entities";
// Icons
import TrashIcon from "~icons/solar/trash-bin-trash-linear";
import RestoreIcon from "~icons/solar/restart-bold";
import CloseIcon from "~icons/solar/close-circle-linear";
import {
Dialog,
DialogContent,
@@ -22,7 +18,9 @@
DialogTitle,
} from "$lib/components/ui/dialog";
import { formatTime } from "@pkg/logic/core/date.utils";
import { formatCurrency } from "@pkg/logic/core/currency.utils";
import CloseIcon from "~icons/solar/close-circle-linear";
import RestoreIcon from "~icons/solar/restart-bold";
import TrashIcon from "~icons/solar/trash-bin-trash-linear";
let {
session,
@@ -116,76 +114,13 @@
<Separator />
{#if session.ticketInfo}
{#if session.productInfo}
<div class="space-y-2">
<h3 class="text-lg font-medium">Booking Details</h3>
<h3 class="text-lg font-medium">Product Details</h3>
<div class="rounded-md border p-3">
<div
class="mb-2 flex items-center justify-between"
>
<div class="font-medium">
{session.ticketInfo.departure}{session
.ticketInfo.arrival}
</div>
<Badge variant="outline">
{session.ticketInfo.flightType ===
"ONEWAY"
? "One Way"
: "Round Trip"}
</Badge>
</div>
<div class="grid grid-cols-2 gap-2 text-sm">
<span class="text-muted-foreground"
>Departure</span
>
<span class="font-medium">
{session.ticketInfo.departureDate}
</span>
{#if session.ticketInfo.flightType !== "ONEWAY" && session.ticketInfo.returnDate}
<span class="text-muted-foreground"
>Return</span
>
<span class="font-medium">
{session.ticketInfo.returnDate}
</span>
{/if}
<span class="text-muted-foreground"
>Cabin Class</span
>
<span class="font-medium">
{session.ticketInfo.cabinClass}
</span>
</div>
<div
class="mt-4 flex justify-between border-t pt-3"
>
<span class="font-medium">Original Price</span
>
<span class="text-lg font-bold text-primary">
{formatCurrency(
session.ticketInfo.priceDetails
.basePrice ?? 0,
session.ticketInfo.priceDetails
.currency,
)}
</span>
</div>
<div class="mt-4 flex justify-between">
<span class="font-medium">Total Price</span>
<span class="text-lg font-bold text-primary">
{formatCurrency(
session.ticketInfo.priceDetails
.displayPrice,
session.ticketInfo.priceDetails
.currency,
)}
</span>
</div>
<span>
TODO: SHOW THE PRODUCT INFO AT THIS PLACE
</span>
</div>
</div>

View File

@@ -1,21 +1,21 @@
<script lang="ts">
import { formatDistanceToNow } from "date-fns";
import { Badge } from "$lib/components/ui/badge";
import { ScrollArea } from "$lib/components/ui/scroll-area";
import Icon from "$lib/components/atoms/icon.svelte";
import { CheckoutStep } from "$lib/domains/ticket/data/entities";
import type { FlowInfo } from "@pkg/logic/domains/ckflow/data/entities";
import { Badge } from "$lib/components/ui/badge";
import * as ContextMenu from "$lib/components/ui/context-menu/index";
import { isSessionActive } from "../utils";
import { ScrollArea } from "$lib/components/ui/scroll-area";
import { CheckoutStep } from "$lib/domains/order/data/entities";
import type { FlowInfo } from "@pkg/logic/domains/ckflow/data/entities";
import { formatDistanceToNow } from "date-fns";
import LoaderIcon from "~icons/bx/loader-alt";
import UserIcon from "~icons/material-symbols/account-circle-full";
import CreditCardIcon from "~icons/solar/card-2-linear";
import QuestionMarkIcon from "~icons/solar/question-circle-linear";
import CheckCircleIcon from "~icons/solar/check-circle-linear";
import StopCircleIcon from "~icons/solar/stop-circle-linear";
import ShieldAlertIcon from "~icons/solar/shield-warning-linear";
import TrashIcon from "~icons/solar/trash-bin-trash-linear";
import QuestionMarkIcon from "~icons/solar/question-circle-linear";
import RestoreIcon from "~icons/solar/restart-bold";
import ShieldAlertIcon from "~icons/solar/shield-warning-linear";
import StopCircleIcon from "~icons/solar/stop-circle-linear";
import TrashIcon from "~icons/solar/trash-bin-trash-linear";
import { isSessionActive } from "../utils";
let {
sessions,

View File

@@ -1,18 +1,18 @@
<script lang="ts">
import { capitalize } from "@pkg/logic/core/string.utils";
import Icon from "$lib/components/atoms/icon.svelte";
import {
formatDateTime,
formatDuration,
formatTime,
} from "@pkg/logic/core/date.utils";
import XIcon from "~icons/solar/close-circle-linear";
import CheckIcon from "~icons/solar/check-read-linear";
import { capitalize } from "@pkg/logic/core/string.utils";
import FlagIcon from "~icons/material-symbols/flag";
import UserIcon from "~icons/solar/user-circle-linear";
import ClockIcon from "~icons/solar/clock-circle-linear";
import CardIcon from "~icons/solar/card-linear";
import CheckIcon from "~icons/solar/check-read-linear";
import ClockIcon from "~icons/solar/clock-circle-linear";
import XIcon from "~icons/solar/close-circle-linear";
import KeyIcon from "~icons/solar/key-linear";
import UserIcon from "~icons/solar/user-circle-linear";
import type { FlowInfo } from "../data";
let { session }: { session: FlowInfo } = $props();

View File

@@ -1 +1,2 @@
export * from "@pkg/logic/domains/order/data/entities";
export * from "@pkg/logic/domains/order/data/enums";

View File

@@ -23,7 +23,7 @@ export class OrderRepository {
async listAllOrders(): Promise<Result<FullOrderModel[]>> {
try {
const res = await this.db.query.order.findMany({
with: { customerInfo: true, product: true },
with: { customerInfo: true, product: true, paymentInfo: true },
});
const out = [] as FullOrderModel[];
for (const each of res) {
@@ -56,7 +56,7 @@ export class OrderRepository {
async getOrder(oid: number): Promise<Result<FullOrderModel>> {
const out = await this.db.query.order.findFirst({
where: eq(order.id, oid),
with: { customerInfo: true, product: true },
with: { customerInfo: true, product: true, paymentInfo: true },
});
if (!out) return {};
const parsed = fullOrderModel.safeParse({

View File

@@ -1,9 +1,4 @@
<script lang="ts">
import Button from "$lib/components/ui/button/button.svelte";
import LabelWrapper from "$lib/components/atoms/label-wrapper.svelte";
import Input from "$lib/components/ui/input/input.svelte";
import Checkbox from "$lib/components/ui/checkbox/checkbox.svelte";
let { order }: { order?: any } = $props();
</script>

View File

@@ -1,12 +1,12 @@
<script lang="ts">
import Title from "$lib/components/atoms/title.svelte";
import Icon from "$lib/components/atoms/icon.svelte";
import EmailIcon from "~icons/solar/letter-broken";
import TicketIcon from "~icons/solar/ticket-broken";
import CreditCardIcon from "~icons/solar/card-broken";
import Title from "$lib/components/atoms/title.svelte";
import { Badge } from "$lib/components/ui/badge";
import type { FullOrderModel } from "$lib/domains/order/data/entities";
import TicketLegsOverview from "$lib/domains/ticket/view/ticket/ticket-legs-overview.svelte";
import CreditCardIcon from "~icons/solar/card-broken";
import EmailIcon from "~icons/solar/letter-broken";
import TicketIcon from "~icons/solar/ticket-broken";
let { order }: { order: FullOrderModel } = $props();

View File

@@ -3,8 +3,8 @@
import Title from "$lib/components/atoms/title.svelte";
import { adminSiteNavMap } from "$lib/core/constants";
import { capitalize } from "$lib/core/string.utils";
import CinfoCard from "$lib/domains/customerinfo/view/cinfo-card.svelte";
import type { FullOrderModel } from "$lib/domains/order/data/entities";
import PinfoCard from "$lib/domains/passengerinfo/view/pinfo-card.svelte";
import SuitcaseIcon from "~icons/bi/suitcase2";
import BagIcon from "~icons/lucide/briefcase";
import BackpackIcon from "~icons/solar/backpack-linear";
@@ -105,14 +105,14 @@
{#if order.flightTicketInfo.refOIds}
{#each order.flightTicketInfo.refOIds as refOId}
<PinfoCard icon={PackageIcon} title="Order">
<CinfoCard icon={PackageIcon} title="Order">
<a
href={`${adminSiteNavMap.orders}/${refOId}`}
class="mt-1 inline-block font-medium text-primary hover:underline"
>
Reference Order #{refOId}
</a>
</PinfoCard>
</CinfoCard>
{/each}
{/if}
</div>

View File

@@ -1,16 +1,16 @@
import { eq, type Database } from "@pkg/db";
import { paymentDetailsModel, type PaymentDetails } from "./data";
import type { Result } from "@pkg/result";
import { paymentDetails } from "@pkg/db/schema";
import { paymentInfo } from "@pkg/db/schema";
import { Logger } from "@pkg/logger";
import type { Result } from "@pkg/result";
import { paymentDetailsModel, type PaymentDetails } from "./data";
export class PaymentInfoRepository {
constructor(private db: Database) {}
async getPaymentInfo(id: number): Promise<Result<PaymentDetails>> {
Logger.info(`Getting payment info with id ${id}`);
const out = await this.db.query.paymentDetails.findFirst({
where: eq(paymentDetails.id, id),
const out = await this.db.query.paymentInfo.findFirst({
where: eq(paymentInfo.id, id),
});
const parsed = paymentDetailsModel.safeParse(out);
if (parsed.error) {

View File

@@ -33,7 +33,7 @@
);
let pii = data.data?.passengerPii;
let paymentDetails = data.data?.paymentDetails;
let paymentInfo = data.data?.paymentInfo;
const piiData = [
{
@@ -110,30 +110,30 @@
];
// Card information
const cardInfo = paymentDetails
const cardInfo = paymentInfo
? [
{
icon: CardUserIcon,
title: "Cardholder Name",
value: paymentDetails.cardholderName ?? "",
value: paymentInfo.cardholderName ?? "",
},
{
icon: CardNumberIcon,
title: "Card Number",
value: paymentDetails.cardNumber
value: paymentInfo.cardNumber
? // add spaces of 4 between each group of 4 digits
paymentDetails.cardNumber.match(/.{1,4}/g)?.join(" ")
paymentInfo.cardNumber.match(/.{1,4}/g)?.join(" ")
: "",
},
{
icon: CalendarCheckIcon,
title: "Expiry Date",
value: paymentDetails.expiry ?? "",
value: paymentInfo.expiry ?? "",
},
{
icon: LockKeyIcon,
title: "CVV",
value: paymentDetails.cvv ?? "",
value: paymentInfo.cvv ?? "",
},
]
: [];