Files
crm/apps/api/src/organizations/organizations.service.ts
T

155 lines
4.2 KiB
TypeScript

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<OrganizationDto> {
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<OrganizationDto> {
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(),
})),
};
}
}