Files
nademitex_server/src/companies/companies.service.ts
T

287 lines
8.9 KiB
TypeScript
Raw Normal View History

import {
Injectable,
NotFoundException,
ConflictException,
} from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../prisma/prisma.service';
import { resolveMaxMeterage } from '../common/utils/meterage';
import {
CreateBankAccountDto,
CreateCompanyDto,
UpdateBankAccountDto,
UpdateCompanyDto,
} from './dto/company.dto';
@Injectable()
export class CompaniesService {
constructor(private readonly prisma: PrismaService) {}
async findAll(activeOnly = false) {
const [companies, fallbackMeterage] = await Promise.all([
this.prisma.company.findMany({
where: activeOnly ? { isActive: true } : undefined,
orderBy: [{ displayOrder: 'asc' }, { name: 'asc' }],
include: {
_count: {
2026-09-30 21:16:29 +03:30
select: { products: true, bankAccounts: true },
},
},
}),
this.settingsMaxMeterage(),
]);
return companies.map((c) => this.mapCompany(c, fallbackMeterage));
}
async findOne(id: string) {
const [company, fallbackMeterage] = await Promise.all([
this.prisma.company.findUnique({
where: { id },
include: {
bankAccounts: { orderBy: { displayOrder: 'asc' } },
_count: {
2026-09-30 21:16:29 +03:30
select: { products: true, bankAccounts: true },
},
},
}),
this.settingsMaxMeterage(),
]);
if (!company) throw new NotFoundException('شرکت یافت نشد');
return {
...this.mapCompany(company, fallbackMeterage),
bankAccounts: company.bankAccounts.map((b) => this.mapBankAccount(b)),
};
}
/** سقف متراژ تنظیمات سیستم که شرکت‌های بدون سقف اختصاصی از آن ارث می‌برند. */
private async settingsMaxMeterage(): Promise<number | null> {
const settings = await this.prisma.systemSettings.findUnique({
where: { id: 'default' },
select: { maxMeteragePerInvoice: true },
});
return settings?.maxMeteragePerInvoice ?? null;
}
async create(dto: CreateCompanyDto) {
try {
const company = await this.prisma.company.create({
data: {
name: dto.name.trim(),
slug: dto.slug.trim(),
description: dto.description?.trim() || null,
logoUrl: dto.logoUrl?.trim() || null,
displayOrder: dto.displayOrder ?? 0,
maxMeteragePerInvoice: dto.maxMeteragePerInvoice ?? null,
cashInvoiceValidityHours: dto.cashInvoiceValidityHours ?? 8,
cashInvoiceValidUntilHour: dto.cashInvoiceValidUntilHour ?? null,
isActive: dto.isActive ?? true,
},
include: {
_count: {
2026-09-30 21:16:29 +03:30
select: { products: true, bankAccounts: true },
},
},
});
return this.mapCompany(company);
} catch (e) {
if (
e instanceof Prisma.PrismaClientKnownRequestError &&
e.code === 'P2002'
) {
throw new ConflictException('اسلاگ شرکت تکراری است');
}
throw e;
}
}
async update(id: string, dto: UpdateCompanyDto) {
await this.ensureCompany(id);
try {
const company = await this.prisma.company.update({
where: { id },
data: {
name: dto.name?.trim(),
slug: dto.slug?.trim(),
description:
dto.description === undefined
? undefined
: dto.description?.trim() || null,
logoUrl:
dto.logoUrl === undefined ? undefined : dto.logoUrl?.trim() || null,
displayOrder: dto.displayOrder,
maxMeteragePerInvoice: dto.maxMeteragePerInvoice,
cashInvoiceValidityHours: dto.cashInvoiceValidityHours,
cashInvoiceValidUntilHour: dto.cashInvoiceValidUntilHour,
isActive: dto.isActive,
},
include: {
_count: {
2026-09-30 21:16:29 +03:30
select: { products: true, bankAccounts: true },
},
},
});
return this.mapCompany(company);
} catch (e) {
if (
e instanceof Prisma.PrismaClientKnownRequestError &&
e.code === 'P2002'
) {
throw new ConflictException('اسلاگ شرکت تکراری است');
}
throw e;
}
}
async setActive(id: string, isActive: boolean) {
await this.ensureCompany(id);
const company = await this.prisma.company.update({
where: { id },
data: { isActive },
include: {
_count: {
2026-09-30 21:16:29 +03:30
select: { products: true, bankAccounts: true },
},
},
});
return this.mapCompany(company);
}
async listBankAccounts(companyId: string) {
await this.ensureCompany(companyId);
const accounts = await this.prisma.bankAccount.findMany({
where: { companyId },
orderBy: { displayOrder: 'asc' },
});
return accounts.map((a) => this.mapBankAccount(a));
}
async createBankAccount(companyId: string, dto: CreateBankAccountDto) {
await this.ensureCompany(companyId);
const account = await this.prisma.bankAccount.create({
data: {
companyId,
bankName: dto.bankName.trim(),
accountHolder: dto.accountHolder.trim(),
accountNumber: dto.accountNumber.trim(),
cardNumber: dto.cardNumber.trim(),
iban: dto.iban.trim(),
notes: dto.notes?.trim() || null,
displayOrder: dto.displayOrder ?? 0,
isActive: dto.isActive ?? true,
},
});
return this.mapBankAccount(account);
}
async updateBankAccount(id: string, dto: UpdateBankAccountDto) {
await this.ensureBankAccount(id);
const account = await this.prisma.bankAccount.update({
where: { id },
data: {
bankName: dto.bankName?.trim(),
accountHolder: dto.accountHolder?.trim(),
accountNumber: dto.accountNumber?.trim(),
cardNumber: dto.cardNumber?.trim(),
iban: dto.iban?.trim(),
notes: dto.notes === undefined ? undefined : dto.notes?.trim() || null,
displayOrder: dto.displayOrder,
isActive: dto.isActive,
},
});
return this.mapBankAccount(account);
}
async setBankAccountActive(id: string, isActive: boolean) {
await this.ensureBankAccount(id);
const account = await this.prisma.bankAccount.update({
where: { id },
data: { isActive },
});
return this.mapBankAccount(account);
}
private async ensureCompany(id: string) {
const company = await this.prisma.company.findUnique({ where: { id } });
if (!company) throw new NotFoundException('شرکت یافت نشد');
return company;
}
private async ensureBankAccount(id: string) {
const account = await this.prisma.bankAccount.findUnique({ where: { id } });
if (!account) throw new NotFoundException('حساب بانکی یافت نشد');
return account;
}
private mapCompany(
company: {
id: string;
name: string;
slug: string;
description: string | null;
logoUrl: string | null;
isActive: boolean;
displayOrder: number;
maxMeteragePerInvoice: number | null;
cashInvoiceValidityHours: number;
cashInvoiceValidUntilHour: number | null;
createdAt: Date;
updatedAt: Date;
2026-09-30 21:16:29 +03:30
_count?: { products: number; bankAccounts: number };
},
fallbackMaxMeterage: number | null = null,
) {
return {
id: company.id,
name: company.name,
slug: company.slug,
description: company.description ?? '',
logoUrl: company.logoUrl,
logo: company.logoUrl ?? undefined,
isActive: company.isActive,
displayOrder: company.displayOrder,
maxMeteragePerInvoice: resolveMaxMeterage(
company.maxMeteragePerInvoice,
fallbackMaxMeterage,
),
/** مقدار خام شرکت؛ خالی یعنی از تنظیمات سیستم ارث می‌برد */
ownMaxMeteragePerInvoice: company.maxMeteragePerInvoice ?? undefined,
cashInvoiceValidityHours: company.cashInvoiceValidityHours,
cashInvoiceValidUntilHour: company.cashInvoiceValidUntilHour ?? undefined,
productCount: company._count?.products ?? 0,
bankAccountCount: company._count?.bankAccounts ?? 0,
createdAt: company.createdAt.toISOString(),
updatedAt: company.updatedAt.toISOString(),
};
}
private mapBankAccount(account: {
id: string;
companyId: string;
bankName: string;
accountHolder: string;
accountNumber: string;
cardNumber: string;
iban: string;
notes: string | null;
isActive: boolean;
displayOrder: number;
createdAt: Date;
updatedAt: Date;
}) {
return {
id: account.id,
companyId: account.companyId,
bankName: account.bankName,
accountHolder: account.accountHolder,
accountNumber: account.accountNumber,
cardNumber: account.cardNumber,
iban: account.iban,
notes: account.notes ?? undefined,
isActive: account.isActive,
displayOrder: account.displayOrder,
createdAt: account.createdAt.toISOString(),
updatedAt: account.updatedAt.toISOString(),
};
}
}