Match list/form mockups on desktop and mobile, wire real APIs for billing fields, and merge proforma into the invoices section. Co-authored-by: Cursor <cursoragent@cursor.com>
318 lines
4.9 KiB
TypeScript
318 lines
4.9 KiB
TypeScript
import {
|
|
ArrayMinSize,
|
|
IsArray,
|
|
IsIn,
|
|
IsNumber,
|
|
IsOptional,
|
|
IsString,
|
|
MaxLength,
|
|
Min,
|
|
MinLength,
|
|
ValidateNested,
|
|
} from 'class-validator';
|
|
import { Type } from 'class-transformer';
|
|
|
|
const INVOICE_STATUSES = [
|
|
'draft',
|
|
'issued',
|
|
'partially_paid',
|
|
'paid',
|
|
'canceled',
|
|
] as const;
|
|
|
|
const INVOICE_KINDS = ['invoice', 'credit'] as const;
|
|
|
|
const PAYMENT_METHODS = [
|
|
'cash',
|
|
'transfer',
|
|
'card',
|
|
'check',
|
|
'other',
|
|
] as const;
|
|
|
|
export class InvoiceLineItemInputDto {
|
|
@IsOptional()
|
|
@IsString()
|
|
productId?: string | null;
|
|
|
|
@IsString()
|
|
@MinLength(1)
|
|
@MaxLength(200)
|
|
name!: string;
|
|
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(0.01)
|
|
quantity!: number;
|
|
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(0)
|
|
unitPrice!: number;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(8)
|
|
currency?: string;
|
|
}
|
|
|
|
export class InvoiceInstallmentInputDto {
|
|
@IsString()
|
|
dueDate!: string;
|
|
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(0.01)
|
|
amount!: number;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(8)
|
|
currency?: string;
|
|
}
|
|
|
|
export class CreateInvoiceDto {
|
|
@IsString()
|
|
@MinLength(2)
|
|
@MaxLength(200)
|
|
title!: string;
|
|
|
|
@IsOptional()
|
|
@IsIn(INVOICE_STATUSES)
|
|
status?: (typeof INVOICE_STATUSES)[number];
|
|
|
|
@IsOptional()
|
|
@IsIn(INVOICE_KINDS)
|
|
kind?: (typeof INVOICE_KINDS)[number];
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(8)
|
|
currency?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(4000)
|
|
notes?: string;
|
|
|
|
@IsOptional()
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(0)
|
|
discountAmount?: number;
|
|
|
|
@IsOptional()
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(0)
|
|
taxPercent?: number;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
issuedAt?: string | null;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
dueDate?: string | null;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
personId?: string | null;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
companyId?: string | null;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
dealId?: string | null;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
quoteId?: string | null;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
fromDealId?: string | null;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
fromQuoteId?: string | null;
|
|
|
|
@IsOptional()
|
|
@IsArray()
|
|
@ValidateNested({ each: true })
|
|
@Type(() => InvoiceLineItemInputDto)
|
|
@ArrayMinSize(0)
|
|
lineItems?: InvoiceLineItemInputDto[];
|
|
|
|
@IsOptional()
|
|
@IsArray()
|
|
@ValidateNested({ each: true })
|
|
@Type(() => InvoiceInstallmentInputDto)
|
|
installments?: InvoiceInstallmentInputDto[];
|
|
}
|
|
|
|
export class UpdateInvoiceDto {
|
|
@IsOptional()
|
|
@IsString()
|
|
@MinLength(2)
|
|
@MaxLength(200)
|
|
title?: string;
|
|
|
|
@IsOptional()
|
|
@IsIn(INVOICE_STATUSES)
|
|
status?: (typeof INVOICE_STATUSES)[number];
|
|
|
|
@IsOptional()
|
|
@IsIn(INVOICE_KINDS)
|
|
kind?: (typeof INVOICE_KINDS)[number];
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(8)
|
|
currency?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(4000)
|
|
notes?: string | null;
|
|
|
|
@IsOptional()
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(0)
|
|
discountAmount?: number;
|
|
|
|
@IsOptional()
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(0)
|
|
taxPercent?: number;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
issuedAt?: string | null;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
dueDate?: string | null;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
personId?: string | null;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
companyId?: string | null;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
dealId?: string | null;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
quoteId?: string | null;
|
|
|
|
@IsOptional()
|
|
@IsArray()
|
|
@ValidateNested({ each: true })
|
|
@Type(() => InvoiceLineItemInputDto)
|
|
lineItems?: InvoiceLineItemInputDto[];
|
|
|
|
@IsOptional()
|
|
@IsArray()
|
|
@ValidateNested({ each: true })
|
|
@Type(() => InvoiceInstallmentInputDto)
|
|
installments?: InvoiceInstallmentInputDto[];
|
|
}
|
|
|
|
export class CreatePaymentDto {
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(0.01)
|
|
amount!: number;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(8)
|
|
currency?: string;
|
|
|
|
@IsOptional()
|
|
@IsIn(PAYMENT_METHODS)
|
|
method?: (typeof PAYMENT_METHODS)[number];
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
paidAt?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
@MaxLength(2000)
|
|
notes?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
installmentId?: string | null;
|
|
}
|
|
|
|
export class ListInvoicesQueryDto {
|
|
@IsOptional()
|
|
@IsString()
|
|
q?: string;
|
|
|
|
@IsOptional()
|
|
@IsIn(INVOICE_STATUSES)
|
|
status?: (typeof INVOICE_STATUSES)[number];
|
|
|
|
@IsOptional()
|
|
@IsIn(INVOICE_KINDS)
|
|
kind?: (typeof INVOICE_KINDS)[number];
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
dealId?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
personId?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
companyId?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
quoteId?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
from?: string;
|
|
|
|
@IsOptional()
|
|
@IsString()
|
|
to?: string;
|
|
|
|
@IsOptional()
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
amountMin?: number;
|
|
|
|
@IsOptional()
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
amountMax?: number;
|
|
|
|
@IsOptional()
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(1)
|
|
page?: number;
|
|
|
|
@IsOptional()
|
|
@Type(() => Number)
|
|
@IsNumber()
|
|
@Min(1)
|
|
limit?: number;
|
|
}
|