manicanldes-backend/src/checkout/order.store.ts

102 lines
3.1 KiB
TypeScript

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);
},
);
}
}