feat: add treasury audit, attachment download, and installment SMS
Let cheque clearance pick a cash account and export income, expense, and installment lists to Excel. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -13,6 +13,8 @@ import type {
|
||||
} from '@mento/shared';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { NotificationsService } from '../notifications/notifications.service';
|
||||
import { SmsService } from '../common/sms.service';
|
||||
import ExcelJS from 'exceljs';
|
||||
import type { Actor } from '../common/record-scope';
|
||||
|
||||
function dec(value: Prisma.Decimal | number | string | null | undefined) {
|
||||
@@ -45,6 +47,7 @@ export class TreasuryReceivablesService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly notifications: NotificationsService,
|
||||
private readonly sms: SmsService,
|
||||
) {}
|
||||
|
||||
async list(actor: Actor): Promise<FinancialReceivableDto[]> {
|
||||
@@ -211,6 +214,27 @@ export class TreasuryReceivablesService {
|
||||
return this.get(actor, installment.receivableId);
|
||||
}
|
||||
|
||||
async excel(actor: Actor) {
|
||||
const rows = await this.list(actor);
|
||||
const book = new ExcelJS.Workbook();
|
||||
const sheet = book.addWorksheet('اقساط');
|
||||
sheet.addRow(['قرارداد', 'طرف حساب', 'قسط', 'سررسید', 'مبلغ', 'مانده', 'وضعیت']);
|
||||
for (const row of rows) {
|
||||
for (const item of row.installments) {
|
||||
sheet.addRow([
|
||||
row.title,
|
||||
row.partyName ?? '',
|
||||
item.position,
|
||||
item.dueDate.slice(0, 10),
|
||||
item.amount,
|
||||
item.remaining,
|
||||
item.status,
|
||||
]);
|
||||
}
|
||||
}
|
||||
return Buffer.from(await book.xlsx.writeBuffer());
|
||||
}
|
||||
|
||||
async upcoming(actor: Actor) {
|
||||
const rows = await this.prisma.financialInstallment.findMany({
|
||||
where: {
|
||||
@@ -264,18 +288,27 @@ export class TreasuryReceivablesService {
|
||||
},
|
||||
select: { userId: true },
|
||||
});
|
||||
const title = dto.status === 'overdue' ? 'قسط معوق' : 'قسط نزدیک سررسید';
|
||||
const body = `${row.receivable.title} · قسط ${row.position} · مانده ${dto.remaining}`;
|
||||
for (const admin of admins) {
|
||||
await this.notifications.create({
|
||||
organizationId: row.receivable.organizationId,
|
||||
userId: admin.userId,
|
||||
type: 'treasury_installment',
|
||||
title: dto.status === 'overdue' ? 'قسط معوق' : 'قسط نزدیک سررسید',
|
||||
body: `${row.receivable.title} · قسط ${row.position}`,
|
||||
title,
|
||||
body,
|
||||
link: '/finance/receivables',
|
||||
entityType: 'financial_installment',
|
||||
entityId: row.id,
|
||||
dedupe: true,
|
||||
});
|
||||
const user = await this.prisma.user.findUnique({
|
||||
where: { id: admin.userId },
|
||||
select: { mobile: true },
|
||||
});
|
||||
if (user?.mobile && this.sms.isConfigured()) {
|
||||
await this.sms.sendSmsDetailed(user.mobile, `${title}: ${body}`).catch(() => undefined);
|
||||
}
|
||||
}
|
||||
await this.prisma.financialInstallment.update({
|
||||
where: { id: row.id },
|
||||
|
||||
Reference in New Issue
Block a user