feat: gate treasury as an org module and group money inputs
Link optional invoice and cheque settlements to cash accounts, and let superadmin enable the finance module per organization. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2,12 +2,13 @@ import { Module } from '@nestjs/common';
|
||||
import { AutomationsModule } from '../automations/automations.module';
|
||||
import { MessageAutomationsModule } from '../message-automations/message-automations.module';
|
||||
import { FinanceModule } from '../finance/finance.module';
|
||||
import { TreasuryModule } from '../treasury/treasury.module';
|
||||
import { InventoryModule } from '../inventory/inventory.module';
|
||||
import { InvoicesController } from './invoices.controller';
|
||||
import { InvoicesService } from './invoices.service';
|
||||
|
||||
@Module({
|
||||
imports: [AutomationsModule, MessageAutomationsModule, InventoryModule, FinanceModule],
|
||||
imports: [AutomationsModule, MessageAutomationsModule, InventoryModule, FinanceModule, TreasuryModule],
|
||||
controllers: [InvoicesController],
|
||||
providers: [InvoicesService],
|
||||
exports: [InvoicesService],
|
||||
|
||||
@@ -22,6 +22,7 @@ import { MessageAutomationsService } from '../message-automations/message-automa
|
||||
import { InventoryService } from '../inventory/inventory.service';
|
||||
import { CommissionsService } from '../finance/commissions.service';
|
||||
import { GamificationService } from '../gamification/gamification.service';
|
||||
import { TreasuryBridgeService } from '../treasury/treasury-bridge.service';
|
||||
|
||||
type InvoiceActor = Pick<Actor, 'userId' | 'role'>;
|
||||
|
||||
@@ -54,6 +55,7 @@ export class InvoicesService {
|
||||
private readonly inventory: InventoryService,
|
||||
private readonly commissions: CommissionsService,
|
||||
private readonly game: GamificationService,
|
||||
private readonly treasury: TreasuryBridgeService,
|
||||
) {}
|
||||
|
||||
async list(
|
||||
@@ -509,9 +511,16 @@ export class InvoicesService {
|
||||
? dto.cheque.status
|
||||
: 'pending',
|
||||
notes: emptyToNull(dto.cheque.notes ?? null),
|
||||
financialAccountId: dto.financialAccountId || null,
|
||||
},
|
||||
});
|
||||
}
|
||||
if (dto.financialAccountId) {
|
||||
await tx.payment.update({
|
||||
where: { id: payment.id },
|
||||
data: { financialAccountId: dto.financialAccountId },
|
||||
});
|
||||
}
|
||||
if (installmentId) {
|
||||
await tx.invoiceInstallment.update({
|
||||
where: { id: installmentId },
|
||||
@@ -530,6 +539,25 @@ export class InvoicesService {
|
||||
return payment.id;
|
||||
});
|
||||
|
||||
const accountId = dto.financialAccountId || null;
|
||||
const chequePending = method === 'check' && dto.cheque?.status !== 'cleared';
|
||||
if (accountId && !chequePending) {
|
||||
const linkedCheque = method === 'check'
|
||||
? await this.prisma.cheque.findFirst({ where: { paymentId }, select: { id: true } })
|
||||
: null;
|
||||
await this.treasury.postCash({
|
||||
organizationId,
|
||||
accountId,
|
||||
amount: dto.amount,
|
||||
direction: 'in',
|
||||
kind: 'receipt',
|
||||
occurredAt: dto.paidAt ? new Date(dto.paidAt) : new Date(),
|
||||
memo: 'پرداخت فاکتور',
|
||||
actorUserId: actor.userId,
|
||||
paymentId,
|
||||
chequeId: linkedCheque?.id ?? null,
|
||||
});
|
||||
}
|
||||
await this.recomputeStatus(invoiceId);
|
||||
const row = await this.get(organizationId, invoiceId, actor);
|
||||
await this.inventory.syncInvoiceStock(organizationId, row.id);
|
||||
@@ -574,6 +602,7 @@ export class InvoicesService {
|
||||
});
|
||||
if (!payment) throw new NotFoundException('پرداخت پیدا نشد');
|
||||
|
||||
await this.treasury.voidPayment(paymentId);
|
||||
await this.prisma.$transaction(async (tx) => {
|
||||
if (payment.installmentId) {
|
||||
await tx.invoiceInstallment.update({
|
||||
|
||||
Reference in New Issue
Block a user