mirror of
https://git.meshkee.com/Meshkee/backend.git
synced 2026-08-11 22:30:59 +04:30
Expand invoices with full templates, public viewer API, and account holder.
Add invoice template CRUD, key points/accounts, public GET endpoint, and migration 039 for account_holder_name. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
426316d53c
commit
3faeb9bc0d
@@ -11,10 +11,15 @@ import { PrismaService } from '../prisma/prisma.service';
|
||||
import {
|
||||
CreateInvoiceDto,
|
||||
CreateInvoiceItemTemplateDto,
|
||||
CreateInvoiceTemplateDto,
|
||||
InvoiceAccountInputDto,
|
||||
InvoiceItemInputDto,
|
||||
InvoiceKeyPointInputDto,
|
||||
InvoiceTemplateItemInputDto,
|
||||
ListInvoicesDto,
|
||||
UpdateInvoiceItemTemplateDto,
|
||||
UpdateInvoiceStatusDto,
|
||||
UpdateInvoiceTemplateDto,
|
||||
} from './dto/invoice.dto';
|
||||
|
||||
@Injectable()
|
||||
@@ -62,6 +67,140 @@ export class InvoicesService {
|
||||
};
|
||||
}
|
||||
|
||||
private readonly invoiceInclude = {
|
||||
business: { select: { id: true, name: true, nameFa: true } },
|
||||
issuer: { select: { id: true, firstName: true, lastName: true } },
|
||||
items: { orderBy: [{ sortOrder: 'asc' as const }, { id: 'asc' as const }] },
|
||||
keyPoints: { orderBy: [{ sortOrder: 'asc' as const }, { id: 'asc' as const }] },
|
||||
accounts: { orderBy: [{ sortOrder: 'asc' as const }, { id: 'asc' as const }] },
|
||||
};
|
||||
|
||||
private readonly invoiceTemplateInclude = {
|
||||
items: { orderBy: [{ sortOrder: 'asc' as const }, { id: 'asc' as const }] },
|
||||
keyPoints: { orderBy: [{ sortOrder: 'asc' as const }, { id: 'asc' as const }] },
|
||||
accounts: { orderBy: [{ sortOrder: 'asc' as const }, { id: 'asc' as const }] },
|
||||
};
|
||||
|
||||
private serializeKeyPoint(row: {
|
||||
id: bigint;
|
||||
text: string;
|
||||
sortOrder: number;
|
||||
}) {
|
||||
return {
|
||||
id: row.id.toString(),
|
||||
text: row.text,
|
||||
sortOrder: row.sortOrder,
|
||||
};
|
||||
}
|
||||
|
||||
private serializeAccount(row: {
|
||||
id: bigint;
|
||||
bankName: string;
|
||||
accountHolderName: string | null;
|
||||
cardNumber: string | null;
|
||||
iban: string | null;
|
||||
sortOrder: number;
|
||||
}) {
|
||||
return {
|
||||
id: row.id.toString(),
|
||||
bankName: row.bankName,
|
||||
accountHolderName: row.accountHolderName,
|
||||
cardNumber: row.cardNumber,
|
||||
iban: row.iban,
|
||||
sortOrder: row.sortOrder,
|
||||
};
|
||||
}
|
||||
|
||||
private serializeTemplateItem(row: {
|
||||
id: bigint;
|
||||
templateId: bigint;
|
||||
itemTemplateId: bigint | null;
|
||||
title: string;
|
||||
duration: string | null;
|
||||
worktime: string | null;
|
||||
description: string | null;
|
||||
price: Prisma.Decimal;
|
||||
discountedPrice: Prisma.Decimal | null;
|
||||
sortOrder: number;
|
||||
}) {
|
||||
return {
|
||||
id: row.id.toString(),
|
||||
templateId: row.templateId.toString(),
|
||||
itemTemplateId: row.itemTemplateId?.toString() ?? null,
|
||||
title: row.title,
|
||||
duration: row.duration,
|
||||
worktime: row.worktime,
|
||||
description: row.description,
|
||||
price: Number(row.price),
|
||||
discountedPrice: row.discountedPrice === null ? null : Number(row.discountedPrice),
|
||||
sortOrder: row.sortOrder,
|
||||
};
|
||||
}
|
||||
|
||||
private serializeInvoiceTemplate(
|
||||
row: {
|
||||
id: bigint;
|
||||
ownerScope: InvoiceOwnerScope;
|
||||
businessId: bigint | null;
|
||||
name: string;
|
||||
topText: string | null;
|
||||
sortOrder: number;
|
||||
isActive: boolean;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
items?: Array<{
|
||||
id: bigint;
|
||||
templateId: bigint;
|
||||
itemTemplateId: bigint | null;
|
||||
title: string;
|
||||
duration: string | null;
|
||||
worktime: string | null;
|
||||
description: string | null;
|
||||
price: Prisma.Decimal;
|
||||
discountedPrice: Prisma.Decimal | null;
|
||||
sortOrder: number;
|
||||
}>;
|
||||
keyPoints?: Array<{
|
||||
id: bigint;
|
||||
text: string;
|
||||
sortOrder: number;
|
||||
}>;
|
||||
accounts?: Array<{
|
||||
id: bigint;
|
||||
bankName: string;
|
||||
accountHolderName: string | null;
|
||||
cardNumber: string | null;
|
||||
iban: string | null;
|
||||
sortOrder: number;
|
||||
}>;
|
||||
},
|
||||
includeNested = true,
|
||||
) {
|
||||
return {
|
||||
id: row.id.toString(),
|
||||
ownerScope: row.ownerScope,
|
||||
businessId: row.businessId?.toString() ?? null,
|
||||
name: row.name,
|
||||
topText: row.topText,
|
||||
sortOrder: row.sortOrder,
|
||||
isActive: row.isActive,
|
||||
createdAt: row.createdAt,
|
||||
updatedAt: row.updatedAt,
|
||||
items:
|
||||
includeNested && row.items
|
||||
? row.items.map((item) => this.serializeTemplateItem(item))
|
||||
: undefined,
|
||||
keyPoints:
|
||||
includeNested && row.keyPoints
|
||||
? row.keyPoints.map((kp) => this.serializeKeyPoint(kp))
|
||||
: undefined,
|
||||
accounts:
|
||||
includeNested && row.accounts
|
||||
? row.accounts.map((acc) => this.serializeAccount(acc))
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
private serializeItem(row: {
|
||||
id: bigint;
|
||||
invoiceId: bigint;
|
||||
@@ -89,6 +228,10 @@ export class InvoicesService {
|
||||
}
|
||||
|
||||
private platformInvoicePublicUrl(invoiceId: bigint) {
|
||||
const base = process.env.INVOICE_PUBLIC_BASE_URL?.trim();
|
||||
if (base) {
|
||||
return `${base.replace(/\/$/, '')}/invoices/${invoiceId.toString()}`;
|
||||
}
|
||||
const domain = process.env.INVOICE_PUBLIC_DOMAIN?.trim() || 'meshkee.com';
|
||||
return `https://${domain}/invoices/${invoiceId.toString()}`;
|
||||
}
|
||||
@@ -101,7 +244,9 @@ export class InvoicesService {
|
||||
issuerBusinessId: bigint | null;
|
||||
status: InvoiceStatus;
|
||||
name: string | null;
|
||||
topText: string | null;
|
||||
notes: string | null;
|
||||
invoiceTemplateId: bigint | null;
|
||||
issuedBy: bigint | null;
|
||||
issuedAt: Date;
|
||||
createdAt: Date;
|
||||
@@ -120,10 +265,24 @@ export class InvoicesService {
|
||||
discountedPrice: Prisma.Decimal | null;
|
||||
sortOrder: number;
|
||||
}>;
|
||||
keyPoints?: Array<{
|
||||
id: bigint;
|
||||
text: string;
|
||||
sortOrder: number;
|
||||
}>;
|
||||
accounts?: Array<{
|
||||
id: bigint;
|
||||
bankName: string;
|
||||
accountHolderName: string | null;
|
||||
cardNumber: string | null;
|
||||
iban: string | null;
|
||||
sortOrder: number;
|
||||
}>;
|
||||
},
|
||||
includeItems = true,
|
||||
includeNested = true,
|
||||
) {
|
||||
const items = includeItems && row.items ? row.items.map((item) => this.serializeItem(item)) : undefined;
|
||||
const items =
|
||||
includeNested && row.items ? row.items.map((item) => this.serializeItem(item)) : undefined;
|
||||
const totals = items
|
||||
? items.reduce(
|
||||
(acc, item) => {
|
||||
@@ -147,7 +306,9 @@ export class InvoicesService {
|
||||
issuerBusinessId: row.issuerBusinessId?.toString() ?? null,
|
||||
status: row.status,
|
||||
name: row.name,
|
||||
topText: row.topText,
|
||||
notes: row.notes,
|
||||
invoiceTemplateId: row.invoiceTemplateId?.toString() ?? null,
|
||||
publicUrl:
|
||||
row.ownerScope === InvoiceOwnerScope.platform
|
||||
? this.platformInvoicePublicUrl(row.id)
|
||||
@@ -171,11 +332,66 @@ export class InvoicesService {
|
||||
}
|
||||
: null,
|
||||
items,
|
||||
keyPoints:
|
||||
includeNested && row.keyPoints
|
||||
? row.keyPoints.map((kp) => this.serializeKeyPoint(kp))
|
||||
: undefined,
|
||||
accounts:
|
||||
includeNested && row.accounts
|
||||
? row.accounts.map((acc) => this.serializeAccount(acc))
|
||||
: undefined,
|
||||
subtotal: totals?.subtotal,
|
||||
total: totals?.total,
|
||||
};
|
||||
}
|
||||
|
||||
private normalizeKeyPointInput(keyPoint: InvoiceKeyPointInputDto) {
|
||||
const text = keyPoint.text.trim();
|
||||
if (!text) {
|
||||
throw new BadRequestException('Each key point requires text');
|
||||
}
|
||||
return { text };
|
||||
}
|
||||
|
||||
private normalizeAccountInput(account: InvoiceAccountInputDto) {
|
||||
const bankName = account.bankName.trim();
|
||||
if (!bankName) {
|
||||
throw new BadRequestException('Each account requires a bank name');
|
||||
}
|
||||
return {
|
||||
bankName,
|
||||
accountHolderName: account.accountHolderName?.trim() || null,
|
||||
cardNumber: account.cardNumber?.trim() || null,
|
||||
iban: account.iban?.trim() || null,
|
||||
};
|
||||
}
|
||||
|
||||
private normalizeTemplateItemInput(item: InvoiceTemplateItemInputDto) {
|
||||
const title = item.title.trim();
|
||||
if (!title) {
|
||||
throw new BadRequestException('Each invoice template item requires a title');
|
||||
}
|
||||
|
||||
const discountedPrice =
|
||||
item.discountedPrice === undefined || item.discountedPrice === null
|
||||
? null
|
||||
: item.discountedPrice;
|
||||
|
||||
if (discountedPrice !== null && discountedPrice > item.price) {
|
||||
throw new BadRequestException('Discounted price cannot exceed price');
|
||||
}
|
||||
|
||||
return {
|
||||
itemTemplateId: item.itemTemplateId?.trim() ? BigInt(item.itemTemplateId) : null,
|
||||
title,
|
||||
duration: item.duration?.trim() || null,
|
||||
worktime: item.worktime?.trim() || null,
|
||||
description: item.description?.trim() || null,
|
||||
price: item.price,
|
||||
discountedPrice,
|
||||
};
|
||||
}
|
||||
|
||||
private normalizeItemInput(item: InvoiceItemInputDto) {
|
||||
const title = item.title.trim();
|
||||
if (!title) {
|
||||
@@ -309,6 +525,235 @@ export class InvoicesService {
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
// --- Invoice templates (platform settings) ---
|
||||
|
||||
async listPlatformInvoiceTemplates(actor: AuthUser) {
|
||||
await this.assertSuperAdmin(actor);
|
||||
|
||||
const rows = await this.prisma.invoiceTemplate.findMany({
|
||||
where: { ownerScope: InvoiceOwnerScope.platform },
|
||||
include: this.invoiceTemplateInclude,
|
||||
orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }],
|
||||
});
|
||||
|
||||
return { items: rows.map((row) => this.serializeInvoiceTemplate(row)) };
|
||||
}
|
||||
|
||||
async getPlatformInvoiceTemplate(templateIdRaw: string, actor: AuthUser) {
|
||||
await this.assertSuperAdmin(actor);
|
||||
|
||||
const templateId = BigInt(templateIdRaw);
|
||||
const row = await this.prisma.invoiceTemplate.findFirst({
|
||||
where: { id: templateId, ownerScope: InvoiceOwnerScope.platform },
|
||||
include: this.invoiceTemplateInclude,
|
||||
});
|
||||
|
||||
if (!row) {
|
||||
throw new NotFoundException('Invoice template not found');
|
||||
}
|
||||
|
||||
return this.serializeInvoiceTemplate(row);
|
||||
}
|
||||
|
||||
async createPlatformInvoiceTemplate(dto: CreateInvoiceTemplateDto, actor: AuthUser) {
|
||||
await this.assertSuperAdmin(actor);
|
||||
|
||||
const name = dto.name.trim();
|
||||
if (!name) {
|
||||
throw new BadRequestException('Name is required');
|
||||
}
|
||||
|
||||
const items = dto.items.map((item) => this.normalizeTemplateItemInput(item));
|
||||
const keyPoints = (dto.keyPoints ?? []).map((kp) => this.normalizeKeyPointInput(kp));
|
||||
const accounts = (dto.accounts ?? []).map((acc) => this.normalizeAccountInput(acc));
|
||||
|
||||
const row = await this.prisma.invoiceTemplate.create({
|
||||
data: {
|
||||
ownerScope: InvoiceOwnerScope.platform,
|
||||
name,
|
||||
topText: dto.topText?.trim() || null,
|
||||
sortOrder: dto.sortOrder ?? 0,
|
||||
items: {
|
||||
create: items.map((item, index) => ({
|
||||
itemTemplateId: item.itemTemplateId,
|
||||
title: item.title,
|
||||
duration: item.duration,
|
||||
worktime: item.worktime,
|
||||
description: item.description,
|
||||
price: item.price,
|
||||
discountedPrice: item.discountedPrice,
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
keyPoints: {
|
||||
create: keyPoints.map((kp, index) => ({
|
||||
text: kp.text,
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
accounts: {
|
||||
create: accounts.map((acc, index) => ({
|
||||
bankName: acc.bankName,
|
||||
accountHolderName: acc.accountHolderName,
|
||||
cardNumber: acc.cardNumber,
|
||||
iban: acc.iban,
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
},
|
||||
include: this.invoiceTemplateInclude,
|
||||
});
|
||||
|
||||
return this.serializeInvoiceTemplate(row);
|
||||
}
|
||||
|
||||
async updatePlatformInvoiceTemplate(
|
||||
templateIdRaw: string,
|
||||
dto: UpdateInvoiceTemplateDto,
|
||||
actor: AuthUser,
|
||||
) {
|
||||
await this.assertSuperAdmin(actor);
|
||||
|
||||
const templateId = BigInt(templateIdRaw);
|
||||
const existing = await this.prisma.invoiceTemplate.findFirst({
|
||||
where: { id: templateId, ownerScope: InvoiceOwnerScope.platform },
|
||||
});
|
||||
if (!existing) {
|
||||
throw new NotFoundException('Invoice template not found');
|
||||
}
|
||||
|
||||
const replaceItems =
|
||||
dto.items !== undefined ? dto.items.map((item) => this.normalizeTemplateItemInput(item)) : null;
|
||||
const replaceKeyPoints =
|
||||
dto.keyPoints !== undefined
|
||||
? dto.keyPoints.map((kp) => this.normalizeKeyPointInput(kp))
|
||||
: null;
|
||||
const replaceAccounts =
|
||||
dto.accounts !== undefined
|
||||
? dto.accounts.map((acc) => this.normalizeAccountInput(acc))
|
||||
: null;
|
||||
|
||||
const row = await this.prisma.$transaction(async (tx) => {
|
||||
if (replaceItems !== null) {
|
||||
await tx.invoiceTemplateItem.deleteMany({ where: { templateId } });
|
||||
}
|
||||
if (replaceKeyPoints !== null) {
|
||||
await tx.invoiceTemplateKeyPoint.deleteMany({ where: { templateId } });
|
||||
}
|
||||
if (replaceAccounts !== null) {
|
||||
await tx.invoiceTemplateAccount.deleteMany({ where: { templateId } });
|
||||
}
|
||||
|
||||
return tx.invoiceTemplate.update({
|
||||
where: { id: templateId },
|
||||
data: {
|
||||
...(dto.name !== undefined ? { name: dto.name.trim() } : {}),
|
||||
...(dto.topText !== undefined ? { topText: dto.topText?.trim() || null } : {}),
|
||||
...(dto.sortOrder !== undefined ? { sortOrder: dto.sortOrder } : {}),
|
||||
...(dto.isActive !== undefined ? { isActive: dto.isActive } : {}),
|
||||
...(replaceItems !== null
|
||||
? {
|
||||
items: {
|
||||
create: replaceItems.map((item, index) => ({
|
||||
itemTemplateId: item.itemTemplateId,
|
||||
title: item.title,
|
||||
duration: item.duration,
|
||||
worktime: item.worktime,
|
||||
description: item.description,
|
||||
price: item.price,
|
||||
discountedPrice: item.discountedPrice,
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(replaceKeyPoints !== null
|
||||
? {
|
||||
keyPoints: {
|
||||
create: replaceKeyPoints.map((kp, index) => ({
|
||||
text: kp.text,
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(replaceAccounts !== null
|
||||
? {
|
||||
accounts: {
|
||||
create: replaceAccounts.map((acc, index) => ({
|
||||
bankName: acc.bankName,
|
||||
accountHolderName: acc.accountHolderName,
|
||||
cardNumber: acc.cardNumber,
|
||||
iban: acc.iban,
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
include: this.invoiceTemplateInclude,
|
||||
});
|
||||
});
|
||||
|
||||
return this.serializeInvoiceTemplate(row);
|
||||
}
|
||||
|
||||
async deletePlatformInvoiceTemplate(templateIdRaw: string, actor: AuthUser) {
|
||||
await this.assertSuperAdmin(actor);
|
||||
|
||||
const templateId = BigInt(templateIdRaw);
|
||||
const existing = await this.prisma.invoiceTemplate.findFirst({
|
||||
where: { id: templateId, ownerScope: InvoiceOwnerScope.platform },
|
||||
});
|
||||
if (!existing) {
|
||||
throw new NotFoundException('Invoice template not found');
|
||||
}
|
||||
|
||||
await this.prisma.invoiceTemplate.delete({ where: { id: templateId } });
|
||||
return { ok: true };
|
||||
}
|
||||
|
||||
// --- Public invoice viewer (platform) ---
|
||||
|
||||
async getPublicInvoice(invoiceIdRaw: string) {
|
||||
let invoiceId: bigint;
|
||||
try {
|
||||
invoiceId = BigInt(invoiceIdRaw);
|
||||
} catch {
|
||||
throw new NotFoundException('Invoice not found');
|
||||
}
|
||||
|
||||
const row = await this.prisma.invoice.findFirst({
|
||||
where: {
|
||||
id: invoiceId,
|
||||
ownerScope: InvoiceOwnerScope.platform,
|
||||
status: { in: [InvoiceStatus.issued, InvoiceStatus.paid] },
|
||||
},
|
||||
include: this.invoiceInclude,
|
||||
});
|
||||
|
||||
if (!row) {
|
||||
throw new NotFoundException('Invoice not found');
|
||||
}
|
||||
|
||||
const serialized = this.serializeInvoice(row, true);
|
||||
// Public payload: no internal notes / issuer identity
|
||||
return {
|
||||
id: serialized.id,
|
||||
status: serialized.status,
|
||||
name: serialized.name,
|
||||
topText: serialized.topText,
|
||||
issuedAt: serialized.issuedAt,
|
||||
business: serialized.business,
|
||||
items: serialized.items,
|
||||
keyPoints: serialized.keyPoints,
|
||||
accounts: serialized.accounts,
|
||||
subtotal: serialized.subtotal,
|
||||
total: serialized.total,
|
||||
publicUrl: serialized.publicUrl,
|
||||
};
|
||||
}
|
||||
|
||||
// --- Invoices for a business ---
|
||||
|
||||
async listForBusiness(businessIdRaw: string, query: ListInvoicesDto, actor: AuthUser) {
|
||||
@@ -335,11 +780,7 @@ export class InvoicesService {
|
||||
this.prisma.invoice.count({ where }),
|
||||
this.prisma.invoice.findMany({
|
||||
where,
|
||||
include: {
|
||||
business: { select: { id: true, name: true, nameFa: true } },
|
||||
issuer: { select: { id: true, firstName: true, lastName: true } },
|
||||
items: { orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }] },
|
||||
},
|
||||
include: this.invoiceInclude,
|
||||
orderBy: { createdAt: 'desc' },
|
||||
skip: (page - 1) * pageSize,
|
||||
take: pageSize,
|
||||
@@ -367,11 +808,7 @@ export class InvoicesService {
|
||||
businessId,
|
||||
ownerScope: InvoiceOwnerScope.platform,
|
||||
},
|
||||
include: {
|
||||
business: { select: { id: true, name: true, nameFa: true } },
|
||||
issuer: { select: { id: true, firstName: true, lastName: true } },
|
||||
items: { orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }] },
|
||||
},
|
||||
include: this.invoiceInclude,
|
||||
});
|
||||
|
||||
if (!row) {
|
||||
@@ -394,6 +831,21 @@ export class InvoicesService {
|
||||
}
|
||||
|
||||
const items = dto.items.map((item) => this.normalizeItemInput(item));
|
||||
const keyPoints = (dto.keyPoints ?? []).map((kp) => this.normalizeKeyPointInput(kp));
|
||||
const accounts = (dto.accounts ?? []).map((acc) => this.normalizeAccountInput(acc));
|
||||
const invoiceTemplateId = dto.invoiceTemplateId?.trim()
|
||||
? BigInt(dto.invoiceTemplateId)
|
||||
: null;
|
||||
|
||||
if (invoiceTemplateId !== null) {
|
||||
const template = await this.prisma.invoiceTemplate.findFirst({
|
||||
where: { id: invoiceTemplateId, ownerScope: InvoiceOwnerScope.platform },
|
||||
select: { id: true },
|
||||
});
|
||||
if (!template) {
|
||||
throw new BadRequestException('Invoice template not found');
|
||||
}
|
||||
}
|
||||
|
||||
const row = await this.prisma.invoice.create({
|
||||
data: {
|
||||
@@ -401,7 +853,9 @@ export class InvoicesService {
|
||||
ownerScope: InvoiceOwnerScope.platform,
|
||||
status: dto.status ?? InvoiceStatus.issued,
|
||||
name: dto.name?.trim() || null,
|
||||
topText: dto.topText?.trim() || null,
|
||||
notes: dto.notes?.trim() || null,
|
||||
invoiceTemplateId,
|
||||
issuedBy: actor.id,
|
||||
items: {
|
||||
create: items.map((item, index) => ({
|
||||
@@ -415,12 +869,23 @@ export class InvoicesService {
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
keyPoints: {
|
||||
create: keyPoints.map((kp, index) => ({
|
||||
text: kp.text,
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
accounts: {
|
||||
create: accounts.map((acc, index) => ({
|
||||
bankName: acc.bankName,
|
||||
accountHolderName: acc.accountHolderName,
|
||||
cardNumber: acc.cardNumber,
|
||||
iban: acc.iban,
|
||||
sortOrder: index,
|
||||
})),
|
||||
},
|
||||
},
|
||||
include: {
|
||||
business: { select: { id: true, name: true, nameFa: true } },
|
||||
issuer: { select: { id: true, firstName: true, lastName: true } },
|
||||
items: { orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }] },
|
||||
},
|
||||
include: this.invoiceInclude,
|
||||
});
|
||||
|
||||
return this.serializeInvoice(row);
|
||||
@@ -454,11 +919,7 @@ export class InvoicesService {
|
||||
status: dto.status,
|
||||
...(dto.notes !== undefined ? { notes: dto.notes?.trim() || null } : {}),
|
||||
},
|
||||
include: {
|
||||
business: { select: { id: true, name: true, nameFa: true } },
|
||||
issuer: { select: { id: true, firstName: true, lastName: true } },
|
||||
items: { orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }] },
|
||||
},
|
||||
include: this.invoiceInclude,
|
||||
});
|
||||
|
||||
return this.serializeInvoice(row);
|
||||
|
||||
Reference in New Issue
Block a user