import { BadRequestException, Injectable, NotFoundException, } from '@nestjs/common'; import type { OrganizationDto, UpdateOrgCurrenciesRequest, } from '@mento/shared'; import { CURRENCY_CATALOG } from '@mento/shared'; import { Prisma } from '@prisma/client'; import { PrismaService } from '../prisma/prisma.service'; import { isKnownCurrency } from '../common/currency'; @Injectable() export class OrganizationsService { constructor(private readonly prisma: PrismaService) {} async getCurrent(organizationId: string): Promise { const org = await this.prisma.organization.findUnique({ where: { id: organizationId }, include: { exchangeRates: { orderBy: { currency: 'asc' } }, }, }); if (!org) { throw new NotFoundException('سازمان پیدا نشد'); } return this.toDto(org); } async updateCurrencies( organizationId: string, dto: UpdateOrgCurrenciesRequest, ): Promise { const org = await this.prisma.organization.findUnique({ where: { id: organizationId }, }); if (!org) throw new NotFoundException('سازمان پیدا نشد'); const baseCurrency = ( dto.baseCurrency?.trim() || org.baseCurrency || 'IRR' ).toUpperCase(); if (!isKnownCurrency(baseCurrency)) { throw new BadRequestException('ارز پایه معتبر نیست'); } let enabled = ( dto.enabledCurrencies?.map((c) => c.trim().toUpperCase()) ?? org.enabledCurrencies ).filter(Boolean); if (!enabled.includes(baseCurrency)) { enabled = [baseCurrency, ...enabled]; } enabled = [...new Set(enabled)]; for (const code of enabled) { if (!isKnownCurrency(code)) { throw new BadRequestException(`ارز ${code} پشتیبانی نمی‌شود`); } } if (dto.rates) { for (const row of dto.rates) { const code = row.currency.trim().toUpperCase(); if (!isKnownCurrency(code)) { throw new BadRequestException(`ارز ${code} پشتیبانی نمی‌شود`); } if (code === baseCurrency) continue; if (row.rateToBase <= 0) { throw new BadRequestException('نرخ باید بزرگ‌تر از صفر باشد'); } } } await this.prisma.$transaction(async (tx) => { await tx.organization.update({ where: { id: organizationId }, data: { baseCurrency, enabledCurrencies: enabled, }, }); if (dto.rates) { for (const row of dto.rates) { const code = row.currency.trim().toUpperCase(); if (code === baseCurrency) { await tx.exchangeRate.deleteMany({ where: { organizationId, currency: code }, }); continue; } await tx.exchangeRate.upsert({ where: { organizationId_currency: { organizationId, currency: code }, }, create: { organizationId, currency: code, rateToBase: new Prisma.Decimal(row.rateToBase), }, update: { rateToBase: new Prisma.Decimal(row.rateToBase), }, }); } } await tx.exchangeRate.deleteMany({ where: { organizationId, OR: [ { currency: baseCurrency }, { currency: { notIn: enabled } }, ], }, }); }); return this.getCurrent(organizationId); } catalog() { return CURRENCY_CATALOG; } private toDto(org: { id: string; name: string; slug: string; baseCurrency: string; enabledCurrencies: string[]; exchangeRates: Array<{ currency: string; rateToBase: Prisma.Decimal; updatedAt: Date; }>; }): OrganizationDto { return { id: org.id, name: org.name, slug: org.slug, baseCurrency: org.baseCurrency, enabledCurrencies: org.enabledCurrencies, exchangeRates: org.exchangeRates.map((r) => ({ currency: r.currency, rateToBase: Number(r.rateToBase), updatedAt: r.updatedAt.toISOString(), })), }; } }