diff --git a/apps/api/src/treasury/treasury-ops.service.ts b/apps/api/src/treasury/treasury-ops.service.ts index 1a7b497..4c1f779 100644 --- a/apps/api/src/treasury/treasury-ops.service.ts +++ b/apps/api/src/treasury/treasury-ops.service.ts @@ -2,6 +2,7 @@ import { BadRequestException, Injectable, Logger, NotFoundException } from '@nes import { Cron } from '@nestjs/schedule'; import { Prisma } from '@prisma/client'; import ExcelJS from 'exceljs'; +import type { TreasuryPartyBalanceDto } from '@mento/shared'; import { PrismaService } from '../prisma/prisma.service'; import type { Actor } from '../common/record-scope'; @@ -314,7 +315,12 @@ export class TreasuryOpsService { return { from: from.toISOString(), to: to.toISOString(), current, previous, ...breakdown }; } - async reportExcel(actor: Actor, from?: string, to?: string) { + async reportExcel( + actor: Actor, + from?: string, + to?: string, + parties?: { debtors: TreasuryPartyBalanceDto[]; creditors: TreasuryPartyBalanceDto[] }, + ) { const data = await this.report(actor, from, to); const book = new ExcelJS.Workbook(); const sheet = book.addWorksheet('گزارش مالی'); @@ -331,10 +337,41 @@ export class TreasuryOpsService { sheet.addRow([]); sheet.addRow(['دسته هزینه', 'مبلغ', 'سهم']); data.current.expenseCategories.forEach((row) => sheet.addRow([row.name, row.amount, row.share])); + if (parties) { + this.addPartySheet(book, 'بدهکاران', parties.debtors, 'دریافت‌شده'); + this.addPartySheet(book, 'بستانکاران', parties.creditors, 'پرداخت‌شده'); + } const buffer = await book.xlsx.writeBuffer(); return Buffer.from(buffer); } + private addPartySheet( + book: ExcelJS.Workbook, + title: string, + rows: TreasuryPartyBalanceDto[], + settledLabel: string, + ) { + const sheet = book.addWorksheet(title); + const kindLabel: Record = { person: 'مشتری', company: 'شرکت', user: 'کاربر' }; + sheet.addRow(['طرف حساب', 'نوع', 'مبلغ کل', settledLabel, 'مانده']); + let amount = new Prisma.Decimal(0); + let settled = new Prisma.Decimal(0); + let remaining = new Prisma.Decimal(0); + for (const row of rows) { + sheet.addRow([ + row.partyName, + (row.partyKind && kindLabel[row.partyKind]) || 'ثبت قبلی', + row.amount, + row.settledAmount, + row.remaining, + ]); + amount = amount.plus(row.amount); + settled = settled.plus(row.settledAmount); + remaining = remaining.plus(row.remaining); + } + sheet.addRow(['جمع', '', money(amount), money(settled), money(remaining)]); + } + private async period(organizationId: string, from: Date, to: Date) { const docs = await this.prisma.financialDocument.findMany({ where: { diff --git a/apps/api/src/treasury/treasury.controller.ts b/apps/api/src/treasury/treasury.controller.ts index c627132..92955dc 100644 --- a/apps/api/src/treasury/treasury.controller.ts +++ b/apps/api/src/treasury/treasury.controller.ts @@ -269,7 +269,11 @@ export class TreasuryController { @Query('from') from?: string, @Query('to') to?: string, ) { - const buffer = await this.ops.reportExcel(user, from, to); + const [debtors, creditors] = await Promise.all([ + this.treasury.partyBalances(user, 'income'), + this.treasury.partyBalances(user, 'expense'), + ]); + const buffer = await this.ops.reportExcel(user, from, to, { debtors, creditors }); return new StreamableFile(buffer, { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', disposition: 'attachment; filename="treasury-report.xlsx"', diff --git a/apps/web/src/views/treasury/TreasuryReportsView.vue b/apps/web/src/views/treasury/TreasuryReportsView.vue index 8d3ac8c..aa9e65e 100644 --- a/apps/web/src/views/treasury/TreasuryReportsView.vue +++ b/apps/web/src/views/treasury/TreasuryReportsView.vue @@ -1,5 +1,6 @@ @@ -108,9 +176,12 @@ onMounted(() => { + - مقایسه دوره‌ها + + + { + + + + + + + + {{ debtors.length }} طرف حساب · جمع مانده {{ toman(sumRemaining(debtors)) }} + + + + خروجی اکسل + + + + + + + + + + + {{ creditors.length }} طرف حساب · جمع مانده {{ toman(sumRemaining(creditors)) }} + + + + خروجی اکسل + + + + + +