feat(F-140): completed feature

This commit is contained in:
chattie
2026-08-22 13:16:27 +02:00
parent 5748c00623
commit 926add3c97
15 changed files with 340 additions and 23 deletions

View File

@@ -497,11 +497,14 @@ function serializeOrder(order: {
totalCents: number;
trackingNumber?: string | null;
courier?: string | null;
customerName?: string | null;
customerPhone?: string | null;
createdAt: Date;
updatedAt: Date;
idempotencyKey: string | null;
userId?: string;
email?: string | null;
phone?: string | null;
items: Array<{
id: string;
productId: string;
@@ -515,11 +518,17 @@ function serializeOrder(order: {
quantity: number;
createdAt: Date;
}>;
shippingAddress?: { recipientName: string; street: string; city: string; postalCode: string; country: string } | null;
billingAddress?: { recipientName: string; street: string; city: string; postalCode: string; country: string } | null;
payment?: { provider: string; status: string; amountCents: number; last4: string | null; createdAt: Date } | null;
}) {
return {
id: order.id,
userId: order.userId,
email: order.email ?? null,
phone: order.phone ?? null,
customerName: order.customerName ?? null,
customerPhone: order.customerPhone ?? null,
state: order.state,
currency: order.currency,
subtotalCents: order.subtotalCents,
@@ -542,6 +551,9 @@ function serializeOrder(order: {
quantity: item.quantity,
createdAt: item.createdAt.toISOString(),
})),
shippingAddress: order.shippingAddress ?? null,
billingAddress: order.billingAddress ?? null,
payment: order.payment ? { ...order.payment, createdAt: order.payment.createdAt.toISOString() } : null,
createdAt: order.createdAt.toISOString(),
updatedAt: order.updatedAt.toISOString(),
};

View File

@@ -28,6 +28,22 @@ export interface OrderItem extends OrderItemInput {
createdAt: Date;
}
export interface Address {
recipientName: string;
street: string;
city: string;
postalCode: string;
country: string;
}
export interface PaymentInfo {
provider: string;
status: string;
amountCents: number;
last4: string | null;
createdAt: Date;
}
export interface Order {
id: string;
userId: string;
@@ -40,6 +56,8 @@ export interface Order {
totalCents: number;
trackingNumber?: string | null;
courier?: string | null;
customerName?: string | null;
customerPhone?: string | null;
createdAt: Date;
updatedAt: Date;
}
@@ -47,6 +65,10 @@ export interface Order {
export interface OrderView extends Order {
items: OrderItem[];
email?: string | null;
phone?: string | null;
shippingAddress?: Address | null;
billingAddress?: Address | null;
payment?: PaymentInfo | null;
}
export const ALLOWED_TRANSITIONS: Readonly<Record<OrderState, ReadonlyArray<OrderState>>> = {

View File

@@ -15,9 +15,28 @@ interface OrderRow {
total_cents: number;
tracking_number: string | null;
courier: string | null;
customer_name: string | null;
customer_phone: string | null;
phone: string | null;
created_at: Date;
updated_at: Date;
}
interface AddressRow {
recipient_name: string;
street: string;
city: string;
postal_code: string;
country: string;
}
interface PaymentRow {
provider: string;
status: string;
amount_cents: number;
provider_payment_id: string | null;
created_at: Date;
}
interface ItemRow {
id: string;
order_id: string;
@@ -154,7 +173,10 @@ export class PgOrderRepository implements OrderRepository {
async findById(id: string): Promise<OrderView | undefined> {
const orderResult = await this.pool.query<OrderRow>(
'SELECT o.*, u.email FROM orders_orders o LEFT JOIN identity_users u ON u.id = o.user_id WHERE o.id = $1',
`SELECT o.*, u.email, u.phone
FROM orders_orders o
LEFT JOIN identity_users u ON u.id = o.user_id
WHERE o.id = $1`,
[id],
);
const order = orderResult.rows[0];
@@ -163,12 +185,32 @@ export class PgOrderRepository implements OrderRepository {
'SELECT * FROM orders_items WHERE order_id = $1 ORDER BY created_at, id',
[id],
);
return toOrderView(order, items.rows.map(toItem));
const [shippingRow] = await this.pool.query<AddressRow>(
`SELECT recipient_name, street, city, postal_code, country
FROM users_addresses WHERE user_id = $1 AND type = 'shipping' LIMIT 1`,
[order.user_id],
).then((r) => r.rows);
const [billingRow] = await this.pool.query<AddressRow>(
`SELECT recipient_name, street, city, postal_code, country
FROM users_addresses WHERE user_id = $1 AND type = 'billing' LIMIT 1`,
[order.user_id],
).then((r) => r.rows);
const [paymentRow] = await this.pool.query<PaymentRow>(
`SELECT provider, status, amount_cents, provider_payment_id, created_at
FROM payments_transactions
WHERE order_id = $1 AND status = 'succeeded'
ORDER BY created_at DESC LIMIT 1`,
[id],
).then((r) => r.rows);
return toOrderView(order, items.rows.map(toItem), shippingRow, billingRow, paymentRow);
}
async findByIdAndUserId(id: string, userId: string): Promise<OrderView | undefined> {
const orderResult = await this.pool.query<OrderRow>(
'SELECT o.*, u.email FROM orders_orders o LEFT JOIN identity_users u ON u.id = o.user_id WHERE o.id = $1 AND o.user_id = $2',
`SELECT o.*, u.email, u.phone
FROM orders_orders o
LEFT JOIN identity_users u ON u.id = o.user_id
WHERE o.id = $1 AND o.user_id = $2`,
[id, userId],
);
const order = orderResult.rows[0];
@@ -257,11 +299,24 @@ function toOrder(row: OrderRow): Order {
};
}
function toOrderView(row: OrderRow, items: OrderItem[]): OrderView {
function toOrderView(
row: OrderRow,
items: OrderItem[],
shipping?: AddressRow | null,
billing?: AddressRow | null,
payment?: PaymentRow | null,
): OrderView {
const last4 = payment?.provider_payment_id
? payment.provider_payment_id.replace(/[^0-9]/g, '').slice(-4)
: null;
return {
...toOrder(row),
email: row.email,
phone: row.phone ?? null,
items,
shippingAddress: shipping ? { recipientName: shipping.recipient_name, street: shipping.street, city: shipping.city, postalCode: shipping.postal_code, country: shipping.country } : null,
billingAddress: billing ? { recipientName: billing.recipient_name, street: billing.street, city: billing.city, postalCode: billing.postal_code, country: billing.country } : null,
payment: payment ? { provider: payment.provider, status: payment.status, amountCents: payment.amount_cents, last4, createdAt: payment.created_at } : null,
};
}