feat: add settlement_type to SalesInvoice model and related DTOs

- Added settlement_type field to SalesInvoice model with ENUM values (CASH, CREDIT, MIXED).
- Updated SalesInvoice aggregate types, input types, and where filters to include settlement_type.
- Modified StatisticsService to calculate credit amounts based on settlement_type.
- Enhanced TspProviderOriginalSendPayloadDto to include settlement_type.
- Updated NamaProvider DTOs and utility functions to handle new settlement_type logic.
- Created migration to add settlement_type column to sales_invoices table and backfill existing records.
This commit is contained in:
2026-05-24 19:40:04 +03:30
parent b53b7d3ed3
commit ea6f1bfdd0
17 changed files with 340 additions and 268 deletions
@@ -23,6 +23,8 @@ export class StatisticsService {
pending_count: number | null
not_sended_amount_sum: number | null
not_sended_count: number | null
credit_amount_sum: number | null
credit_count: number | null
}>
>`
SELECT
@@ -35,19 +37,21 @@ export class StatisticsService {
SUM(CASE WHEN la.status = ${TspProviderResponseStatus.QUEUED} THEN si.total_amount ELSE 0 END) AS pending_amount_sum,
SUM(CASE WHEN la.status = ${TspProviderResponseStatus.QUEUED} THEN 1 ELSE 0 END) AS pending_count,
SUM(CASE WHEN la.status IS NULL THEN si.total_amount ELSE 0 END) AS not_sended_amount_sum,
SUM(CASE WHEN la.status IS NULL THEN 1 ELSE 0 END) AS not_sended_count
SUM(CASE WHEN la.status IS NULL THEN 1 ELSE 0 END) AS not_sended_count,
SUM(CASE WHEN si.settlement_type = 'CREDIT' THEN si.total_amount ELSE 0 END) AS credit_amount_sum,
SUM(CASE WHEN si.settlement_type = 'CREDIT' THEN 1 ELSE 0 END) AS credit_count
FROM sales_invoices si
INNER JOIN poses p ON p.id = si.pos_id
LEFT JOIN (
SELECT a1.invoice_id, a1.status
FROM sale_invoice_tsp_attempts a1
SELECT att.invoice_id, att.status
FROM sale_invoice_tsp_attempts att
INNER JOIN (
SELECT invoice_id, MAX(attempt_no) AS max_attempt_no
FROM sale_invoice_tsp_attempts
GROUP BY invoice_id
) latest
ON latest.invoice_id = a1.invoice_id
AND latest.max_attempt_no = a1.attempt_no
ON latest.invoice_id = att.invoice_id
AND latest.max_attempt_no = att.attempt_no
) la ON la.invoice_id = si.id
WHERE si.invoice_date >= ${seasonDate.start}
AND si.invoice_date <= ${seasonDate.end}
@@ -55,6 +59,11 @@ export class StatisticsService {
AND p.complex_id IN (
SELECT c.id FROM complexes c WHERE c.business_activity_id = ${businessId}
)
AND NOT EXISTS (
SELECT 1
FROM sales_invoices child
WHERE child.ref_id = si.id
)
`
return ResponseMapper.single({
@@ -83,6 +92,11 @@ export class StatisticsService {
total_tax: Number(0),
count: Number(item?.not_sended_count || 0),
},
credit: {
total_amount: Number(item?.credit_amount_sum || 0),
total_tax: Number(0),
count: Number(item?.credit_count || 0),
},
})
}
}
@@ -5,6 +5,7 @@ import {
} from '@/common/services/saleInvoices/sale-invoice-create.dto'
import {
CustomerType,
InvoiceSettlementType,
InvoiceTemplateType,
PaymentMethodType,
TspProviderRequestType,
@@ -208,6 +209,10 @@ export class TspProviderOriginalSendPayloadDto {
@ApiProperty({ required: true })
@IsString()
tsp_token: string
@ApiProperty({ required: true, enum: InvoiceSettlementType })
@IsEnum(InvoiceSettlementType)
settlement_type: InvoiceSettlementType
}
export class TspProviderSendItemResultDto {
@@ -288,6 +288,7 @@ export class SalesInvoiceTspService {
items: dataToUpdate.items,
total_amount: dataToUpdate.total_amount,
customer_id: relatedInvoice.customer_id || undefined,
settlement_type: relatedInvoice.settlement_type,
},
businessId,
complexId,
@@ -320,8 +321,7 @@ export class SalesInvoiceTspService {
)
if (result?.hasError) {
result.provider_request_payload =
result.provider_request_payload ||
JSON.parse(JSON.stringify(correctionPayload))
result.provider_request_payload || JSON.parse(JSON.stringify(correctionPayload))
result.sent_at = result.sent_at || new Date().toISOString()
result.received_at = result.received_at || new Date().toISOString()
}
@@ -472,6 +472,7 @@ export class SalesInvoiceTspService {
customer_type: relatedInvoice.customer?.type || CustomerType.UNKNOWN,
invoice_date: new Date(),
payments,
settlement_type: relatedInvoice.settlement_type,
items: relatedInvoice.items.map(item => ({
unit_price: Number(item.unit_price),
quantity: Number(item.quantity),
@@ -168,9 +168,17 @@ export class NamaProviderHeaderDto {
@IsString()
bid: string
@ApiProperty({ required: true, description: 'روش تسویه' })
@ApiProperty({ required: true, description: 'روش تسویه (۱.نقدی، ۲.نسیه، ۳.نقدی-نسیه)' })
@IsString()
setm: string
setm: '1' | '2' | '3'
@ApiProperty({ description: 'سهم پرداخت نقدی در صورتی که نقدی - نسیه باشد (۳)' })
@IsString()
cap: string
@ApiProperty({ description: 'سهم پرداخت نسیه در صورتی که نقدی - نسیه باشد (۳)' })
@IsString()
insp: string
}
export class NamaProviderRequestDto {
@@ -508,9 +516,17 @@ export class NamaProviderCorrectionHeaderDto {
@IsString()
bid: string
@ApiProperty({ required: true, description: 'روش تسویه' })
@ApiProperty({ required: true, description: 'روش تسویه (۱.نقدی، ۲.نسیه، ۳.نقدی-نسیه)' })
@IsString()
setm: string
setm: '1' | '2' | '3'
@ApiProperty({ description: 'سهم پرداخت نقدی در صورتی که نقدی - نسیه باشد (۳)' })
@IsString()
cap: string
@ApiProperty({ description: 'سهم پرداخت نسیه در صورتی که نقدی - نسیه باشد (۳)' })
@IsString()
insp: string
}
export class NamaProviderCorrectionRequestDto {
@@ -1,6 +1,7 @@
import { TspProviderCustomerType } from '@/common/enums/enums'
import {
CustomerType,
InvoiceSettlementType,
InvoiceTemplateType,
PaymentMethodType,
TspProviderRequestType,
@@ -54,7 +55,11 @@ export class NamaProviderUtils {
inno: payload.invoice_number.toString(),
tins: '',
indatim: payload.invoice_date.getTime() + '',
setm: '1',
...this.mapSettlementBased(
payload.settlement_type,
payload.payments,
payload.total_amount,
),
...this.mapCustomerInfo(payload.customer),
},
body: payload.items.map(item => ({
@@ -134,6 +139,41 @@ export class NamaProviderUtils {
}
}
private mapSettlementType(type: InvoiceSettlementType) {
switch (type) {
case InvoiceSettlementType.CASH:
return '1'
case InvoiceSettlementType.CREDIT:
return '2'
case InvoiceSettlementType.MIXED:
return '3'
}
}
private mapSettlementBased(
type: InvoiceSettlementType,
payments: PaymentInfoDto[],
totalAmount: number,
) {
switch (type) {
case InvoiceSettlementType.CASH:
return {
setm: '1',
}
case InvoiceSettlementType.CREDIT:
return {
setm: '2',
}
case InvoiceSettlementType.MIXED:
const totalPayed = payments.reduce((prev, curr) => (prev += curr.amount), 0)
return {
setm: '3',
cap: totalPayed.toString(),
insp: (totalAmount - totalPayed).toString(),
}
}
}
private mapInvoiceTemplate(template: InvoiceTemplateType) {
switch (template) {
case InvoiceTemplateType.SALE: