Add invoice bank account templates API and migration.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
503b70a690
commit
2a01873c95
@@ -0,0 +1,27 @@
|
|||||||
|
-- Reusable bank account templates (platform or business), parallel to invoice_item_templates.
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS invoice_account_templates (
|
||||||
|
id BIGSERIAL PRIMARY KEY,
|
||||||
|
owner_scope invoice_owner_scope NOT NULL,
|
||||||
|
business_id BIGINT NULL REFERENCES businesses (id) ON DELETE CASCADE ON UPDATE NO ACTION,
|
||||||
|
bank_name VARCHAR(255) NOT NULL,
|
||||||
|
account_holder_name VARCHAR(255) NULL,
|
||||||
|
card_number VARCHAR(64) NULL,
|
||||||
|
iban VARCHAR(64) NULL,
|
||||||
|
sort_order INT NOT NULL DEFAULT 0,
|
||||||
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_invoice_account_templates_owner_scope
|
||||||
|
ON invoice_account_templates (owner_scope, sort_order);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_invoice_account_templates_business_id
|
||||||
|
ON invoice_account_templates (business_id);
|
||||||
|
|
||||||
|
DROP TRIGGER IF EXISTS invoice_account_templates_set_updated_at ON invoice_account_templates;
|
||||||
|
CREATE TRIGGER invoice_account_templates_set_updated_at
|
||||||
|
BEFORE UPDATE ON invoice_account_templates
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE FUNCTION set_updated_at();
|
||||||
@@ -143,6 +143,7 @@ model Business {
|
|||||||
expertReviews ExpertReview[]
|
expertReviews ExpertReview[]
|
||||||
favorites Favorite[]
|
favorites Favorite[]
|
||||||
invoiceItemTemplates InvoiceItemTemplate[]
|
invoiceItemTemplates InvoiceItemTemplate[]
|
||||||
|
invoiceAccountTemplates InvoiceAccountTemplate[]
|
||||||
invoiceTemplates InvoiceTemplate[]
|
invoiceTemplates InvoiceTemplate[]
|
||||||
invoices Invoice[] @relation("InvoiceBusiness")
|
invoices Invoice[] @relation("InvoiceBusiness")
|
||||||
invoicesIssued Invoice[] @relation("InvoiceIssuerBusiness")
|
invoicesIssued Invoice[] @relation("InvoiceIssuerBusiness")
|
||||||
@@ -1370,6 +1371,25 @@ model InvoiceItemTemplate {
|
|||||||
@@map("invoice_item_templates")
|
@@map("invoice_item_templates")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model InvoiceAccountTemplate {
|
||||||
|
id BigInt @id @default(autoincrement())
|
||||||
|
ownerScope InvoiceOwnerScope @map("owner_scope")
|
||||||
|
businessId BigInt? @map("business_id")
|
||||||
|
bankName String @map("bank_name") @db.VarChar(255)
|
||||||
|
accountHolderName String? @map("account_holder_name") @db.VarChar(255)
|
||||||
|
cardNumber String? @map("card_number") @db.VarChar(64)
|
||||||
|
iban String? @db.VarChar(64)
|
||||||
|
sortOrder Int @default(0) @map("sort_order")
|
||||||
|
isActive Boolean @default(true) @map("is_active")
|
||||||
|
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
|
||||||
|
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
|
||||||
|
business Business? @relation(fields: [businessId], references: [id], onDelete: Cascade, onUpdate: NoAction)
|
||||||
|
|
||||||
|
@@index([ownerScope, sortOrder], map: "idx_invoice_account_templates_owner_scope")
|
||||||
|
@@index([businessId], map: "idx_invoice_account_templates_business_id")
|
||||||
|
@@map("invoice_account_templates")
|
||||||
|
}
|
||||||
|
|
||||||
model InvoiceTemplate {
|
model InvoiceTemplate {
|
||||||
id BigInt @id @default(autoincrement())
|
id BigInt @id @default(autoincrement())
|
||||||
ownerScope InvoiceOwnerScope @map("owner_scope")
|
ownerScope InvoiceOwnerScope @map("owner_scope")
|
||||||
|
|||||||
@@ -297,6 +297,65 @@ export class UpdateInvoiceItemTemplateDto {
|
|||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CreateInvoiceAccountTemplateDto {
|
||||||
|
@IsString()
|
||||||
|
@MinLength(1)
|
||||||
|
@MaxLength(255)
|
||||||
|
bankName!: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(255)
|
||||||
|
accountHolderName?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(64)
|
||||||
|
cardNumber?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(64)
|
||||||
|
iban?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsInt()
|
||||||
|
sortOrder?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateInvoiceAccountTemplateDto {
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MinLength(1)
|
||||||
|
@MaxLength(255)
|
||||||
|
bankName?: string;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(255)
|
||||||
|
accountHolderName?: string | null;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(64)
|
||||||
|
cardNumber?: string | null;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@MaxLength(64)
|
||||||
|
iban?: string | null;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@Type(() => Number)
|
||||||
|
@IsInt()
|
||||||
|
sortOrder?: number;
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsBoolean()
|
||||||
|
isActive?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export class InvoiceTemplateItemInputDto {
|
export class InvoiceTemplateItemInputDto {
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsString()
|
@IsString()
|
||||||
|
|||||||
@@ -20,9 +20,11 @@ import { CurrentUser } from '../auth/decorators/current-user.decorator';
|
|||||||
import { AuthUser } from '../auth/auth.types';
|
import { AuthUser } from '../auth/auth.types';
|
||||||
import {
|
import {
|
||||||
CreateInvoiceDto,
|
CreateInvoiceDto,
|
||||||
|
CreateInvoiceAccountTemplateDto,
|
||||||
CreateInvoiceItemTemplateDto,
|
CreateInvoiceItemTemplateDto,
|
||||||
CreateInvoiceTemplateDto,
|
CreateInvoiceTemplateDto,
|
||||||
ListInvoicesDto,
|
ListInvoicesDto,
|
||||||
|
UpdateInvoiceAccountTemplateDto,
|
||||||
UpdateInvoiceDto,
|
UpdateInvoiceDto,
|
||||||
UpdateInvoiceItemTemplateDto,
|
UpdateInvoiceItemTemplateDto,
|
||||||
UpdateInvoiceStatusDto,
|
UpdateInvoiceStatusDto,
|
||||||
@@ -63,6 +65,41 @@ export class InvoicesController {
|
|||||||
return this.service.deletePlatformTemplate(templateId, user);
|
return this.service.deletePlatformTemplate(templateId, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Platform invoice account templates (settings)
|
||||||
|
@Get('invoice-account-templates')
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
listAccountTemplates(@CurrentUser() user: AuthUser) {
|
||||||
|
return this.service.listPlatformAccountTemplates(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('invoice-account-templates')
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
createAccountTemplate(
|
||||||
|
@Body() dto: CreateInvoiceAccountTemplateDto,
|
||||||
|
@CurrentUser() user: AuthUser,
|
||||||
|
) {
|
||||||
|
return this.service.createPlatformAccountTemplate(dto, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch('invoice-account-templates/:templateId')
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
updateAccountTemplate(
|
||||||
|
@Param('templateId') templateId: string,
|
||||||
|
@Body() dto: UpdateInvoiceAccountTemplateDto,
|
||||||
|
@CurrentUser() user: AuthUser,
|
||||||
|
) {
|
||||||
|
return this.service.updatePlatformAccountTemplate(templateId, dto, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('invoice-account-templates/:templateId')
|
||||||
|
@UseGuards(JwtAuthGuard)
|
||||||
|
deleteAccountTemplate(
|
||||||
|
@Param('templateId') templateId: string,
|
||||||
|
@CurrentUser() user: AuthUser,
|
||||||
|
) {
|
||||||
|
return this.service.deletePlatformAccountTemplate(templateId, user);
|
||||||
|
}
|
||||||
|
|
||||||
// Platform invoice templates (settings)
|
// Platform invoice templates (settings)
|
||||||
@Get('invoice-templates')
|
@Get('invoice-templates')
|
||||||
@UseGuards(JwtAuthGuard)
|
@UseGuards(JwtAuthGuard)
|
||||||
@@ -237,6 +274,47 @@ export class BusinessInvoicesController {
|
|||||||
return this.service.deleteBusinessItemTemplate(businessId, templateId, user);
|
return this.service.deleteBusinessItemTemplate(businessId, templateId, user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Invoice account templates (business-scoped)
|
||||||
|
@Get('invoice-account-templates')
|
||||||
|
@RequireBusinessPermission('invoice_templates.read')
|
||||||
|
listAccountTemplatesBusiness(
|
||||||
|
@Param('businessId') businessId: string,
|
||||||
|
@CurrentUser() user: AuthUser,
|
||||||
|
) {
|
||||||
|
return this.service.listBusinessAccountTemplates(businessId, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('invoice-account-templates')
|
||||||
|
@RequireBusinessPermission('invoice_templates.manage')
|
||||||
|
createAccountTemplateBusiness(
|
||||||
|
@Param('businessId') businessId: string,
|
||||||
|
@Body() dto: CreateInvoiceAccountTemplateDto,
|
||||||
|
@CurrentUser() user: AuthUser,
|
||||||
|
) {
|
||||||
|
return this.service.createBusinessAccountTemplate(businessId, dto, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch('invoice-account-templates/:templateId')
|
||||||
|
@RequireBusinessPermission('invoice_templates.manage')
|
||||||
|
updateAccountTemplateBusiness(
|
||||||
|
@Param('businessId') businessId: string,
|
||||||
|
@Param('templateId') templateId: string,
|
||||||
|
@Body() dto: UpdateInvoiceAccountTemplateDto,
|
||||||
|
@CurrentUser() user: AuthUser,
|
||||||
|
) {
|
||||||
|
return this.service.updateBusinessAccountTemplate(businessId, templateId, dto, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('invoice-account-templates/:templateId')
|
||||||
|
@RequireBusinessPermission('invoice_templates.manage')
|
||||||
|
deleteAccountTemplateBusiness(
|
||||||
|
@Param('businessId') businessId: string,
|
||||||
|
@Param('templateId') templateId: string,
|
||||||
|
@CurrentUser() user: AuthUser,
|
||||||
|
) {
|
||||||
|
return this.service.deleteBusinessAccountTemplate(businessId, templateId, user);
|
||||||
|
}
|
||||||
|
|
||||||
// Invoice templates (business-scoped)
|
// Invoice templates (business-scoped)
|
||||||
@Get('invoice-templates')
|
@Get('invoice-templates')
|
||||||
@RequireBusinessPermission('invoice_templates.read')
|
@RequireBusinessPermission('invoice_templates.read')
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { PermissionsService } from '../auth/permissions.service';
|
|||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
import {
|
import {
|
||||||
CreateInvoiceDto,
|
CreateInvoiceDto,
|
||||||
|
CreateInvoiceAccountTemplateDto,
|
||||||
CreateInvoiceItemTemplateDto,
|
CreateInvoiceItemTemplateDto,
|
||||||
CreateInvoiceTemplateDto,
|
CreateInvoiceTemplateDto,
|
||||||
InvoiceAccountInputDto,
|
InvoiceAccountInputDto,
|
||||||
@@ -19,6 +20,7 @@ import {
|
|||||||
InvoicePaymentMethodInputDto,
|
InvoicePaymentMethodInputDto,
|
||||||
InvoiceTemplateItemInputDto,
|
InvoiceTemplateItemInputDto,
|
||||||
ListInvoicesDto,
|
ListInvoicesDto,
|
||||||
|
UpdateInvoiceAccountTemplateDto,
|
||||||
UpdateInvoiceDto,
|
UpdateInvoiceDto,
|
||||||
UpdateInvoiceItemTemplateDto,
|
UpdateInvoiceItemTemplateDto,
|
||||||
UpdateInvoiceStatusDto,
|
UpdateInvoiceStatusDto,
|
||||||
@@ -232,6 +234,34 @@ export class InvoicesService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private serializeAccountTemplate(row: {
|
||||||
|
id: bigint;
|
||||||
|
ownerScope: InvoiceOwnerScope;
|
||||||
|
businessId: bigint | null;
|
||||||
|
bankName: string;
|
||||||
|
accountHolderName: string | null;
|
||||||
|
cardNumber: string | null;
|
||||||
|
iban: string | null;
|
||||||
|
sortOrder: number;
|
||||||
|
isActive: boolean;
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
}) {
|
||||||
|
return {
|
||||||
|
id: row.id.toString(),
|
||||||
|
ownerScope: row.ownerScope,
|
||||||
|
businessId: row.businessId?.toString() ?? null,
|
||||||
|
bankName: row.bankName,
|
||||||
|
accountHolderName: row.accountHolderName,
|
||||||
|
cardNumber: row.cardNumber,
|
||||||
|
iban: row.iban,
|
||||||
|
sortOrder: row.sortOrder,
|
||||||
|
isActive: row.isActive,
|
||||||
|
createdAt: row.createdAt,
|
||||||
|
updatedAt: row.updatedAt,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private readonly invoiceInclude = {
|
private readonly invoiceInclude = {
|
||||||
business: {
|
business: {
|
||||||
select: {
|
select: {
|
||||||
@@ -794,6 +824,83 @@ export class InvoicesService {
|
|||||||
return { ok: true };
|
return { ok: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Account templates (platform settings) ---
|
||||||
|
|
||||||
|
async listPlatformAccountTemplates(actor: AuthUser) {
|
||||||
|
await this.assertSuperAdmin(actor);
|
||||||
|
const rows = await this.prisma.invoiceAccountTemplate.findMany({
|
||||||
|
where: { ownerScope: InvoiceOwnerScope.platform },
|
||||||
|
orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }],
|
||||||
|
});
|
||||||
|
return { items: rows.map((row) => this.serializeAccountTemplate(row)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
async createPlatformAccountTemplate(
|
||||||
|
dto: CreateInvoiceAccountTemplateDto,
|
||||||
|
actor: AuthUser,
|
||||||
|
) {
|
||||||
|
await this.assertSuperAdmin(actor);
|
||||||
|
const bankName = dto.bankName.trim();
|
||||||
|
if (!bankName) {
|
||||||
|
throw new BadRequestException('Bank name is required');
|
||||||
|
}
|
||||||
|
const row = await this.prisma.invoiceAccountTemplate.create({
|
||||||
|
data: {
|
||||||
|
ownerScope: InvoiceOwnerScope.platform,
|
||||||
|
bankName,
|
||||||
|
accountHolderName: dto.accountHolderName?.trim() || null,
|
||||||
|
cardNumber: dto.cardNumber?.trim() || null,
|
||||||
|
iban: dto.iban?.trim() || null,
|
||||||
|
sortOrder: dto.sortOrder ?? 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return this.serializeAccountTemplate(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updatePlatformAccountTemplate(
|
||||||
|
templateIdRaw: string,
|
||||||
|
dto: UpdateInvoiceAccountTemplateDto,
|
||||||
|
actor: AuthUser,
|
||||||
|
) {
|
||||||
|
await this.assertSuperAdmin(actor);
|
||||||
|
const templateId = BigInt(templateIdRaw);
|
||||||
|
const existing = await this.prisma.invoiceAccountTemplate.findFirst({
|
||||||
|
where: { id: templateId, ownerScope: InvoiceOwnerScope.platform },
|
||||||
|
});
|
||||||
|
if (!existing) {
|
||||||
|
throw new NotFoundException('Invoice account template not found');
|
||||||
|
}
|
||||||
|
const row = await this.prisma.invoiceAccountTemplate.update({
|
||||||
|
where: { id: templateId },
|
||||||
|
data: {
|
||||||
|
...(dto.bankName !== undefined ? { bankName: dto.bankName.trim() } : {}),
|
||||||
|
...(dto.accountHolderName !== undefined
|
||||||
|
? { accountHolderName: dto.accountHolderName?.trim() || null }
|
||||||
|
: {}),
|
||||||
|
...(dto.cardNumber !== undefined
|
||||||
|
? { cardNumber: dto.cardNumber?.trim() || null }
|
||||||
|
: {}),
|
||||||
|
...(dto.iban !== undefined ? { iban: dto.iban?.trim() || null } : {}),
|
||||||
|
...(dto.sortOrder !== undefined ? { sortOrder: dto.sortOrder } : {}),
|
||||||
|
...(dto.isActive !== undefined ? { isActive: dto.isActive } : {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return this.serializeAccountTemplate(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deletePlatformAccountTemplate(templateIdRaw: string, actor: AuthUser) {
|
||||||
|
await this.assertSuperAdmin(actor);
|
||||||
|
const templateId = BigInt(templateIdRaw);
|
||||||
|
const existing = await this.prisma.invoiceAccountTemplate.findFirst({
|
||||||
|
where: { id: templateId, ownerScope: InvoiceOwnerScope.platform },
|
||||||
|
});
|
||||||
|
if (!existing) {
|
||||||
|
throw new NotFoundException('Invoice account template not found');
|
||||||
|
}
|
||||||
|
await this.prisma.invoiceAccountTemplate.delete({ where: { id: templateId } });
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
// --- Invoice templates (platform settings) ---
|
// --- Invoice templates (platform settings) ---
|
||||||
|
|
||||||
async listPlatformInvoiceTemplates(actor: AuthUser) {
|
async listPlatformInvoiceTemplates(actor: AuthUser) {
|
||||||
@@ -1129,6 +1236,105 @@ export class InvoicesService {
|
|||||||
return { ok: true };
|
return { ok: true };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Account templates (business-scoped) ---
|
||||||
|
|
||||||
|
async listBusinessAccountTemplates(businessIdRaw: string, actor: AuthUser) {
|
||||||
|
const businessId = BigInt(businessIdRaw);
|
||||||
|
await this.assertBusinessExists(businessId);
|
||||||
|
await this.assertPermission(businessId, actor.id, 'invoice_templates.read');
|
||||||
|
|
||||||
|
const rows = await this.prisma.invoiceAccountTemplate.findMany({
|
||||||
|
where: { ownerScope: InvoiceOwnerScope.business, businessId },
|
||||||
|
orderBy: [{ sortOrder: 'asc' }, { id: 'asc' }],
|
||||||
|
});
|
||||||
|
return { items: rows.map((row) => this.serializeAccountTemplate(row)) };
|
||||||
|
}
|
||||||
|
|
||||||
|
async createBusinessAccountTemplate(
|
||||||
|
businessIdRaw: string,
|
||||||
|
dto: CreateInvoiceAccountTemplateDto,
|
||||||
|
actor: AuthUser,
|
||||||
|
) {
|
||||||
|
const businessId = BigInt(businessIdRaw);
|
||||||
|
await this.assertBusinessExists(businessId);
|
||||||
|
await this.assertPermission(businessId, actor.id, 'invoice_templates.manage');
|
||||||
|
|
||||||
|
const bankName = dto.bankName.trim();
|
||||||
|
if (!bankName) {
|
||||||
|
throw new BadRequestException('Bank name is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
const row = await this.prisma.invoiceAccountTemplate.create({
|
||||||
|
data: {
|
||||||
|
ownerScope: InvoiceOwnerScope.business,
|
||||||
|
businessId,
|
||||||
|
bankName,
|
||||||
|
accountHolderName: dto.accountHolderName?.trim() || null,
|
||||||
|
cardNumber: dto.cardNumber?.trim() || null,
|
||||||
|
iban: dto.iban?.trim() || null,
|
||||||
|
sortOrder: dto.sortOrder ?? 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return this.serializeAccountTemplate(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateBusinessAccountTemplate(
|
||||||
|
businessIdRaw: string,
|
||||||
|
templateIdRaw: string,
|
||||||
|
dto: UpdateInvoiceAccountTemplateDto,
|
||||||
|
actor: AuthUser,
|
||||||
|
) {
|
||||||
|
const businessId = BigInt(businessIdRaw);
|
||||||
|
await this.assertBusinessExists(businessId);
|
||||||
|
await this.assertPermission(businessId, actor.id, 'invoice_templates.manage');
|
||||||
|
|
||||||
|
const templateId = BigInt(templateIdRaw);
|
||||||
|
const existing = await this.prisma.invoiceAccountTemplate.findFirst({
|
||||||
|
where: { id: templateId, ownerScope: InvoiceOwnerScope.business, businessId },
|
||||||
|
});
|
||||||
|
if (!existing) {
|
||||||
|
throw new NotFoundException('Invoice account template not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
const row = await this.prisma.invoiceAccountTemplate.update({
|
||||||
|
where: { id: templateId },
|
||||||
|
data: {
|
||||||
|
...(dto.bankName !== undefined ? { bankName: dto.bankName.trim() } : {}),
|
||||||
|
...(dto.accountHolderName !== undefined
|
||||||
|
? { accountHolderName: dto.accountHolderName?.trim() || null }
|
||||||
|
: {}),
|
||||||
|
...(dto.cardNumber !== undefined
|
||||||
|
? { cardNumber: dto.cardNumber?.trim() || null }
|
||||||
|
: {}),
|
||||||
|
...(dto.iban !== undefined ? { iban: dto.iban?.trim() || null } : {}),
|
||||||
|
...(dto.sortOrder !== undefined ? { sortOrder: dto.sortOrder } : {}),
|
||||||
|
...(dto.isActive !== undefined ? { isActive: dto.isActive } : {}),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return this.serializeAccountTemplate(row);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteBusinessAccountTemplate(
|
||||||
|
businessIdRaw: string,
|
||||||
|
templateIdRaw: string,
|
||||||
|
actor: AuthUser,
|
||||||
|
) {
|
||||||
|
const businessId = BigInt(businessIdRaw);
|
||||||
|
await this.assertBusinessExists(businessId);
|
||||||
|
await this.assertPermission(businessId, actor.id, 'invoice_templates.manage');
|
||||||
|
|
||||||
|
const templateId = BigInt(templateIdRaw);
|
||||||
|
const existing = await this.prisma.invoiceAccountTemplate.findFirst({
|
||||||
|
where: { id: templateId, ownerScope: InvoiceOwnerScope.business, businessId },
|
||||||
|
});
|
||||||
|
if (!existing) {
|
||||||
|
throw new NotFoundException('Invoice account template not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.prisma.invoiceAccountTemplate.delete({ where: { id: templateId } });
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
// --- Invoice templates (business-scoped) ---
|
// --- Invoice templates (business-scoped) ---
|
||||||
|
|
||||||
async listBusinessInvoiceTemplates(businessIdRaw: string, actor: AuthUser) {
|
async listBusinessInvoiceTemplates(businessIdRaw: string, actor: AuthUser) {
|
||||||
|
|||||||
Reference in New Issue
Block a user