155 lines
4.2 KiB
TypeScript
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(),
|
|
})),
|
|
};
|
|
}
|
|
}
|