102 lines
3.1 KiB
TypeScript
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);
|
|
},
|
|
);
|
|
}
|
|
}
|