287 lines
8.9 KiB
TypeScript
287 lines
8.9 KiB
TypeScript
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: {
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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: {
|
|
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;
|
|
_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(),
|
|
};
|
|
}
|
|
}
|