import { BadRequestException, Injectable, NotFoundException, } from '@nestjs/common'; import type { ChequeDto, CreateChequeRequest, UpdateChequeRequest, } from '@mento/shared'; import { Prisma } from '@prisma/client'; import { PrismaService } from '../prisma/prisma.service'; import { type ScopedActor, ownerWhere, scopedOwnerIds } from '../common/record-scope'; import { buildSearchWhere, ci } from '../common/search'; const CHEQUE_STATUSES = ['pending', 'cleared', 'bounced'] as const; @Injectable() export class ChequesService { constructor(private readonly prisma: PrismaService) {} async list( organizationId: string, actor: ScopedActor, opts?: { status?: string; q?: string; direction?: string }, ): Promise { const rows = await this.prisma.cheque.findMany({ where: { organizationId, ...this.accessWhere(actor), ...(opts?.status ? { status: opts.status } : {}), ...(opts?.direction === 'in' || opts?.direction === 'out' ? { direction: opts.direction } : {}), ...this.searchWhere(opts?.q), }, include: this.include, orderBy: [{ dueDate: 'asc' }, { createdAt: 'desc' }], take: 300, }); return rows.map((row) => this.toDto(row)); } private searchWhere(q?: string): Prisma.ChequeWhereInput { const search = buildSearchWhere(q, (t) => [ { number: ci(t) }, { bank: ci(t) }, { invoice: { number: ci(t) } }, { person: { OR: [ { firstName: ci(t) }, { lastName: ci(t) }, { mobile: { contains: t } }, ], }, }, ]); return search ? { AND: [search] } : {}; } async create( organizationId: string, actor: ScopedActor, dto: CreateChequeRequest, ): Promise { const person = await this.prisma.person.findFirst({ where: { id: dto.personId, organizationId, ...ownerWhere(actor) }, select: { id: true }, }); if (!person) throw new BadRequestException('شخص معتبر نیست'); if (!(dto.amount > 0)) { throw new BadRequestException('مبلغ چک لازم است'); } if (!dto.number?.trim() || !dto.bank?.trim() || !dto.dueDate) { throw new BadRequestException('شماره، بانک و سررسید چک لازم است'); } const row = await this.prisma.cheque.create({ data: { organizationId, personId: person.id, ownerId: actor.userId, amount: new Prisma.Decimal(dto.amount), currency: (dto.currency ?? 'IRR').trim() || 'IRR', number: dto.number.trim(), bank: dto.bank.trim(), dueDate: new Date(dto.dueDate), direction: dto.direction === 'out' ? 'out' : 'in', status: dto.status === 'cleared' || dto.status === 'bounced' ? dto.status : 'pending', notes: dto.notes?.trim() ? dto.notes.trim() : null, }, include: this.include, }); return this.toDto(row); } async update( organizationId: string, id: string, actor: ScopedActor, dto: UpdateChequeRequest, ): Promise { const existing = await this.prisma.cheque.findFirst({ where: { id, organizationId, ...this.accessWhere(actor) }, select: { id: true }, }); if (!existing) throw new NotFoundException('چک پیدا نشد'); const data: Prisma.ChequeUncheckedUpdateInput = {}; if (dto.number !== undefined) data.number = dto.number.trim(); if (dto.bank !== undefined) data.bank = dto.bank.trim(); if (dto.dueDate !== undefined) data.dueDate = new Date(dto.dueDate); if (dto.status !== undefined) { if (!CHEQUE_STATUSES.includes(dto.status as (typeof CHEQUE_STATUSES)[number])) { throw new BadRequestException('وضعیت چک معتبر نیست'); } data.status = dto.status; } if ('notes' in dto) { data.notes = dto.notes?.trim() ? dto.notes.trim() : null; } const row = await this.prisma.cheque.update({ where: { id }, data, include: this.include, }); return this.toDto(row); } private accessWhere( actor: ScopedActor, ): Prisma.ChequeWhereInput { const ids = scopedOwnerIds(actor, 'finance'); if (ids === null) return {}; return { OR: [{ ownerId: { in: ids } }, { invoice: { ownerId: { in: ids } } }], }; } private include = { payment: { select: { id: true, amount: true, currency: true, paidAt: true } }, person: { select: { firstName: true, lastName: true } }, invoice: { select: { id: true, number: true, title: true, person: { select: { firstName: true, lastName: true } }, company: { select: { name: true } }, }, }, } satisfies Prisma.ChequeInclude; private toDto( row: Prisma.ChequeGetPayload<{ include: ChequesService['include'] }>, ): ChequeDto { const personName = row.person ? `${row.person.firstName} ${row.person.lastName}`.trim() : row.invoice?.person ? `${row.invoice.person.firstName} ${row.invoice.person.lastName}`.trim() : row.invoice?.company?.name ?? null; return { id: row.id, paymentId: row.paymentId, invoiceId: row.invoiceId, invoiceNumber: row.invoice?.number ?? null, invoiceTitle: row.invoice?.title ?? null, personId: row.personId, personName, amount: Number(row.amount ?? row.payment?.amount ?? 0), currency: row.currency || row.payment?.currency || 'IRR', number: row.number, bank: row.bank, dueDate: row.dueDate.toISOString(), status: row.status, direction: row.direction === 'out' ? 'out' : 'in', notes: row.notes, paidAt: (row.payment?.paidAt ?? row.createdAt).toISOString(), createdAt: row.createdAt.toISOString(), updatedAt: row.updatedAt.toISOString(), }; } }