feat(customers): add DTOs for individual and legal customer creation

- Created CreateCustomerIndividualDto for individual customer data with fields: first_name, last_name, national_code, postal_code, is_favorite, and economic_code.
- Created CreateCustomerLegalDto for legal customer data with fields: company_name, economic_code, registration_number, and postal_code.
This commit is contained in:
2026-02-24 12:42:10 +03:30
parent c7cd7cd855
commit b949500482
27 changed files with 4116 additions and 600 deletions
@@ -0,0 +1,32 @@
import { ApiProperty } from '@nestjs/swagger'
import { IsBoolean, IsNumber, IsString, MaxLength, MinLength } from 'class-validator'
export class CreateCustomerIndividualDto {
@IsString()
@ApiProperty({ required: true })
first_name: string
@IsString()
@ApiProperty({ required: true })
last_name: string
@IsNumber()
@MaxLength(10)
@MinLength(10)
@ApiProperty({ required: true, maxLength: 10, minLength: 10 })
national_code: number
@IsNumber()
@MaxLength(10)
@MinLength(10)
@ApiProperty({ required: true, maxLength: 10, minLength: 10 })
postal_code: number
@IsBoolean()
@ApiProperty({ required: false, default: false })
is_favorite: boolean
@IsNumber()
@ApiProperty({ required: false })
economic_code: number
}
@@ -0,0 +1,22 @@
import { ApiProperty } from '@nestjs/swagger'
import { IsNumber, IsString, MaxLength, MinLength } from 'class-validator'
export class CreateCustomerLegalDto {
@IsString()
@ApiProperty({ required: true })
company_name: string
@IsString()
@ApiProperty({ required: true })
economic_code: string
@IsString()
@ApiProperty({ required: true })
registration_number: string
@IsNumber()
@MaxLength(10)
@MinLength(10)
@ApiProperty({ required: true, maxLength: 10, minLength: 10 })
postal_code: number
}
@@ -1,7 +1,7 @@
import { ApiProperty, PartialType } from '@nestjs/swagger'
import { IsEnum, IsNumber, IsObject, IsOptional, IsString } from 'class-validator'
import type { SaleInvoicePayload } from '../../../common/interfaces/sale-invoice-payload'
import { UnitType } from '../../../generated/prisma/enums'
import type { SaleInvoiceStandardPayload } from '../../../common/interfaces/sale-invoice-payload'
import { SalesInvoiceItemPricingModel, UnitType } from '../../../generated/prisma/enums'
export class CreateSalesInvoiceItemDto {
@IsNumber()
@@ -34,10 +34,20 @@ export class CreateSalesInvoiceItemDto {
@ApiProperty({ enum: Object.values(UnitType) })
unit_type: UnitType
@IsEnum(SalesInvoiceItemPricingModel)
@ApiProperty({ enum: Object.values(SalesInvoiceItemPricingModel) })
@IsOptional()
pricingModel: SalesInvoiceItemPricingModel
@IsOptional()
@IsObject()
@ApiProperty({ required: false, default: {} })
payload?: SaleInvoicePayload
payload?: SaleInvoiceStandardPayload
@IsOptional()
@IsString()
@ApiProperty({ required: false, default: '' })
notes: string
}
export class UpdateSalesInvoiceItemDto extends PartialType(CreateSalesInvoiceItemDto) {}
@@ -1,24 +1,62 @@
import { ApiProperty, PartialType } from '@nestjs/swagger'
import { ArrayMinSize, IsNumber, IsOptional, IsString } from 'class-validator'
import {
ArrayMinSize,
IsDateString,
IsEnum,
IsNumber,
IsObject,
IsOptional,
IsString,
} from 'class-validator'
import type { CustomerIndividual, CustomerLegal } from '../../../generated/prisma/client'
import { CustomerType } from '../../../generated/prisma/enums'
import { CreateSalesInvoiceItemDto } from '../../sales-invoice-items/dto/create-sales-invoice-item.dto'
export class CreateSalesInvoiceDto {
@IsString()
@ApiProperty()
code: string
@IsNumber()
@ApiProperty({ required: true, default: 0 })
total_amount: number
@ApiProperty({ required: true })
@IsDateString({ strict: true }, { message: 'invoice_date must be a valid ISO-8601 string' })
invoice_date: Date
@ApiProperty({ required: true })
@IsObject()
payments: {
terminal: number
cash: number
set_off: number
}
@ApiProperty()
@ArrayMinSize(1)
items: CreateSalesInvoiceItemDto[]
@ApiProperty({ required: false })
@IsOptional()
@IsString()
notes?: string
@ApiProperty({ required: true, default: CustomerType.UNKNOWN })
@IsEnum(CustomerType)
customer_type: CustomerType
@IsOptional()
@IsString()
@ApiProperty({ required: false })
customer_id?: string
@ApiProperty()
@ArrayMinSize(1)
items: CreateSalesInvoiceItemDto[]
@ApiProperty({ required: false })
@IsOptional()
@IsObject()
customer?:
| {
first_name: string
last_name: string
}
| CustomerIndividual
| CustomerLegal
}
export class UpdateSalesInvoiceDto extends PartialType(CreateSalesInvoiceDto) {}
@@ -1,4 +1,6 @@
import { Body, Controller, Get, Param, Post } from '@nestjs/common'
import { reqTokenPayload } from '../auth/current-user.decorator'
import { CreateSalesInvoiceDto } from './dto/create-sales-invoice.dto'
import { SalesInvoicesService } from './sales-invoices.service'
@Controller('sales_invoices')
@@ -16,7 +18,7 @@ export class SalesInvoicesController {
}
@Post()
create(@Body() data: any) {
return this.salesInvoicesService.create(data)
create(@Body() data: CreateSalesInvoiceDto, @reqTokenPayload() account) {
return this.salesInvoicesService.create(data, account)
}
}
@@ -1,7 +1,38 @@
import { Injectable } from '@nestjs/common'
import { ResponseMapper } from '../../common/response/response-mapper'
import type { CustomerIndividual, CustomerLegal } from '../../generated/prisma/client'
import { CustomerType, PaymentMethodType } from '../../generated/prisma/enums'
import { PrismaService } from '../../prisma/prisma.service'
import { CreateSalesInvoiceDto } from './dto/create-sales-invoice.dto'
// Define type guard for CustomerIndividual
function isCustomerIndividual(customer: any): customer is CustomerIndividual {
console.log(
customer &&
typeof customer.first_name === 'string' &&
typeof customer.last_name === 'string',
)
return (
customer &&
typeof customer.first_name === 'string' &&
typeof customer.last_name === 'string'
)
}
// Define type guard for CustomerLegal
function isCustomerLegal(customer: any): customer is CustomerLegal {
return (
customer &&
typeof customer.company_name === 'string' &&
typeof customer.registration_number === 'string'
)
}
@Injectable()
export class SalesInvoicesService {
constructor(private prisma: PrismaService) {}
findAll() {
// TODO: Implement fetching all sales invoices
return []
@@ -12,8 +43,152 @@ export class SalesInvoicesService {
return {}
}
create(data: any) {
// TODO: Implement sales invoice creation
return data
async create(data: CreateSalesInvoiceDto, account) {
data.invoice_date = new Date(data.invoice_date).toISOString() as any
const salesInvoice = await this.prisma.$transaction(async $tx => {
const payments = Object.entries(data.payments)
.filter(([_, value]) => value > 0 && PaymentMethodType[_.toUpperCase()])
.map(([key, value]) => {
return {
amount: value,
payment_method: key.toLocaleUpperCase() as PaymentMethodType,
paid_at: data.invoice_date,
}
})
const totalPayments = payments.reduce((sum, payment) => sum + payment.amount, 0)
if (totalPayments !== data.total_amount) {
throw new Error('مبلغ پرداختی باید برابر با مبلغ کل فاکتور باشد.')
}
const { customer_id, customer_type, customer, ...invoiceData } = data
let newCustomerId: string | null = customer_id || null
if (customer_id) {
} else if (
customer_type === CustomerType.INDIVIDUAL &&
isCustomerIndividual(customer)
) {
let customerIndividual = await $tx.customerIndividual.findUnique({
where: {
complex_id_national_id: {
complex_id: account.complex_id,
national_id: customer.national_id,
},
},
})
if (customerIndividual) {
await $tx.customerIndividual.update({
where: {
complex_id_national_id: {
complex_id: account.complex_id,
national_id: customer.national_id,
},
},
data: {
...customer,
},
})
} else {
const customerIndividual = await $tx.customerIndividual.create({
data: {
...customer,
},
})
const createdCustomer = await $tx.customer.create({
data: {
type: CustomerType.INDIVIDUAL,
complex_id: account.complex_id,
customerIndividuals: {
connect: {
customer_id: customerIndividual.customer_id,
},
},
},
})
if (createdCustomer && createdCustomer.id) {
newCustomerId = createdCustomer.id
}
}
} else if (data.customer_type === CustomerType.LEGAL && isCustomerLegal(customer)) {
let customerLegal = await $tx.customerLegal.findUnique({
where: {
complex_id_registration_number: {
complex_id: account.complex_id,
registration_number: customer.registration_number,
},
},
})
if (customerLegal) {
await $tx.customerLegal.update({
where: {
complex_id_registration_number: {
complex_id: account.complex_id,
registration_number: customer.registration_number,
},
},
data: {
...customer,
},
})
} else {
const customerLegal = await $tx.customerLegal.create({
data: {
...customer,
},
})
const createdCustomer = await $tx.customer.create({
data: {
type: CustomerType.LEGAL,
complex_id: account.complex_id,
customerIndividuals: {
connect: {
customer_id: customerLegal.customer_id,
},
},
},
})
if (createdCustomer && createdCustomer.id) {
newCustomerId = createdCustomer.id
}
}
}
const salesInvoice = await $tx.salesInvoice.create({
data: {
...invoiceData,
account_id: account.account_id,
customer_id: newCustomerId,
total_amount: data.total_amount,
code: 'INV-' + Date.now(),
complex_id: account.complex_id,
items: {
createMany: {
data: data.items.map(item => ({
good_id: item.good_id,
quantity: item.quantity,
unit_price: item.unit_price,
total_amount: item.total_amount,
payload: item.payload,
unit_type: item.unit_type,
pricing_model: item.pricingModel,
})),
},
},
payments: {
createMany: {
data: payments,
},
},
},
})
return salesInvoice
})
return ResponseMapper.create(salesInvoice)
}
}