import { Injectable } from '@nestjs/common'; import { DatabaseService } from '../database/database.service'; import { AccessStore } from '../identity/access.store'; import type { Principal } from '../identity/identity.types'; import { recordAudit } from '../identity/audit'; import { AppError } from '../common/errors/app-error'; import { lockStock } from '../inventory/stock-lock'; import { orderView, orderStatus } from './order-view'; import { enqueueEvent } from '../events/enqueue-event'; @Injectable() export class OrderStore { constructor( private readonly db: DatabaseService, private readonly access: AccessStore, ) {} list( actor: Principal, page: { limit: number; offset: number }, staff = false, ) { return this.db.order .findMany({ where: { organizationId: actor.organizationId, ...(!staff ? { userId: actor.userId } : {}), }, take: page.limit, skip: page.offset, orderBy: [{ createdAt: 'desc' }, { id: 'desc' }], select: { id: true, status: true, currency: true, merchandiseTotal: true, createdAt: true, expiresAt: true, }, }) .then((orders) => orders.map((order) => ({ ...order, status: orderStatus(order), })), ); } async get(actor: Principal, id: string, staff = false) { const order = await this.db.order.findFirst({ where: { id, organizationId: actor.organizationId, ...(!staff ? { userId: actor.userId } : {}), }, include: { lines: { orderBy: { variantId: 'asc' } }, pricing: true }, }); if (!order) throw new AppError('ORDER_NOT_FOUND'); return orderView(order); } cancel(actor: Principal, id: string, staff = false) { return this.access.mutate( actor, staff ? 'orders.manage' : null, async (tx) => { const order = await tx.order.findFirst({ where: { id, organizationId: actor.organizationId, ...(!staff ? { userId: actor.userId } : {}), }, include: { lines: true, pricing: true, reservations: { orderBy: { stockItemId: 'asc' } }, }, }); if (!order) throw new AppError('ORDER_NOT_FOUND'); if (order.status === 'CANCELLED') return orderView(order); for (const reservation of order.reservations) await lockStock(tx, actor.organizationId, reservation.stockItemId); await tx.stockReservation.updateMany({ where: { orderId: id, status: 'ACTIVE' }, data: { status: 'RELEASED' }, }); const updated = await tx.order.update({ where: { id }, data: { status: 'CANCELLED' }, include: { lines: true, pricing: true }, }); await enqueueEvent(tx, actor.organizationId, id, 'order.cancelled'); await recordAudit( tx, actor.organizationId, actor.userId, 'order.cancelled', id, ); return orderView(updated); }, ); } }