Let cheques be received or paid without an invoice, and remind staff three days before due date.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3,10 +3,14 @@ import {
|
||||
Injectable,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import type { ChequeDto, UpdateChequeRequest } from '@mento/shared';
|
||||
import type {
|
||||
ChequeDto,
|
||||
CreateChequeRequest,
|
||||
UpdateChequeRequest,
|
||||
} from '@mento/shared';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { type Actor, ownerWhere } from '../common/record-scope';
|
||||
import { type Actor, elevated, ownerWhere } from '../common/record-scope';
|
||||
|
||||
const CHEQUE_STATUSES = ['pending', 'cleared', 'bounced'] as const;
|
||||
|
||||
@@ -17,13 +21,16 @@ export class ChequesService {
|
||||
async list(
|
||||
organizationId: string,
|
||||
actor: Pick<Actor, 'userId' | 'role'>,
|
||||
opts?: { status?: string; q?: string },
|
||||
opts?: { status?: string; q?: string; direction?: string },
|
||||
): Promise<ChequeDto[]> {
|
||||
const rows = await this.prisma.cheque.findMany({
|
||||
where: {
|
||||
organizationId,
|
||||
invoice: ownerWhere(actor),
|
||||
...this.accessWhere(actor),
|
||||
...(opts?.status ? { status: opts.status } : {}),
|
||||
...(opts?.direction === 'in' || opts?.direction === 'out'
|
||||
? { direction: opts.direction }
|
||||
: {}),
|
||||
...(opts?.q?.trim()
|
||||
? {
|
||||
OR: [
|
||||
@@ -34,6 +41,24 @@ export class ChequesService {
|
||||
number: { contains: opts.q.trim(), mode: 'insensitive' },
|
||||
},
|
||||
},
|
||||
{
|
||||
person: {
|
||||
OR: [
|
||||
{
|
||||
firstName: {
|
||||
contains: opts.q.trim(),
|
||||
mode: 'insensitive',
|
||||
},
|
||||
},
|
||||
{
|
||||
lastName: {
|
||||
contains: opts.q.trim(),
|
||||
mode: 'insensitive',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
@@ -45,6 +70,45 @@ export class ChequesService {
|
||||
return rows.map((row) => this.toDto(row));
|
||||
}
|
||||
|
||||
async create(
|
||||
organizationId: string,
|
||||
actor: Pick<Actor, 'userId' | 'role'>,
|
||||
dto: CreateChequeRequest,
|
||||
): Promise<ChequeDto> {
|
||||
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,
|
||||
@@ -52,7 +116,7 @@ export class ChequesService {
|
||||
dto: UpdateChequeRequest,
|
||||
): Promise<ChequeDto> {
|
||||
const existing = await this.prisma.cheque.findFirst({
|
||||
where: { id, organizationId, invoice: ownerWhere(actor) },
|
||||
where: { id, organizationId, ...this.accessWhere(actor) },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!existing) throw new NotFoundException('چک پیدا نشد');
|
||||
@@ -79,8 +143,18 @@ export class ChequesService {
|
||||
return this.toDto(row);
|
||||
}
|
||||
|
||||
private accessWhere(
|
||||
actor: Pick<Actor, 'userId' | 'role'>,
|
||||
): Prisma.ChequeWhereInput {
|
||||
if (elevated(actor.role)) return {};
|
||||
return {
|
||||
OR: [{ ownerId: actor.userId }, { invoice: { ownerId: actor.userId } }],
|
||||
};
|
||||
}
|
||||
|
||||
private include = {
|
||||
payment: { select: { id: true, amount: true, currency: true, paidAt: true } },
|
||||
person: { select: { firstName: true, lastName: true } },
|
||||
invoice: {
|
||||
select: {
|
||||
id: true,
|
||||
@@ -95,24 +169,28 @@ export class ChequesService {
|
||||
private toDto(
|
||||
row: Prisma.ChequeGetPayload<{ include: ChequesService['include'] }>,
|
||||
): ChequeDto {
|
||||
const personName = row.invoice.person
|
||||
? `${row.invoice.person.firstName} ${row.invoice.person.lastName}`.trim()
|
||||
: row.invoice.company?.name ?? null;
|
||||
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,
|
||||
invoiceTitle: row.invoice.title,
|
||||
invoiceNumber: row.invoice?.number ?? null,
|
||||
invoiceTitle: row.invoice?.title ?? null,
|
||||
personId: row.personId,
|
||||
personName,
|
||||
amount: Number(row.payment.amount),
|
||||
currency: row.payment.currency,
|
||||
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.toISOString(),
|
||||
paidAt: (row.payment?.paidAt ?? row.createdAt).toISOString(),
|
||||
createdAt: row.createdAt.toISOString(),
|
||||
updatedAt: row.updatedAt.toISOString(),
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user