feat: add stock keeping unit management with create, update, and retrieval functionalities
- Implemented CreateStockKeepingUnitDto for creating stock keeping units. - Added StockKeepingUnitsService for handling business logic related to stock keeping units. - Created StockKeepingUnitsController to manage HTTP requests for stock keeping units. - Developed UpdateStockKeepingUnitDto for updating existing stock keeping units. - Introduced StockKeepingUnitsServiceFindAllResponseDto for response structure. - Established stock keeping units module for encapsulation of related components. feat: enhance sales invoice fiscal management with new endpoints - Created SalesInvoicesFilterDto for filtering sales invoices. - Implemented PosSalesInvoiceFiscalController for managing fiscal operations on sales invoices. - Developed PosSalesInvoiceFiscalService to handle fiscal logic and interactions. - Added methods for sending, retrying, and checking the status of fiscal invoices. - Integrated error handling and response mapping for fiscal operations.
This commit is contained in:
@@ -1,12 +1,18 @@
|
||||
import { IPosPayload } from '@/common/models/posPayload.model'
|
||||
import { translateEnumValue } from '@/common/utils'
|
||||
import { PrismaService } from '@/prisma/prisma.service'
|
||||
import { BadRequestException, Injectable } from '@nestjs/common'
|
||||
import { ResponseMapper } from 'common/response/response-mapper'
|
||||
import type { CustomerIndividual, CustomerLegal } from 'generated/prisma/client'
|
||||
import { Prisma } from 'generated/prisma/client'
|
||||
import { CustomerType, PaymentMethodType } from 'generated/prisma/enums'
|
||||
import {
|
||||
CustomerType,
|
||||
FiscalResponseStatus,
|
||||
PaymentMethodType,
|
||||
} from 'generated/prisma/enums'
|
||||
import { SalesInvoiceFiscalService } from '../../consumer/saleInvoices/fiscal/sales-invoice-fiscal.service'
|
||||
import { CreateSalesInvoiceDto } from './dto/create-sales-invoice.dto'
|
||||
import { SalesInvoicesFilterDto } from './dto/sales-invoices-filter.dto'
|
||||
|
||||
// Define type guard for CustomerIndividual
|
||||
function isCustomerIndividual(customer: any): customer is CustomerIndividual {
|
||||
@@ -50,14 +56,213 @@ export class SalesInvoicesService {
|
||||
private salesInvoiceTaxService: SalesInvoiceFiscalService,
|
||||
) {}
|
||||
|
||||
findAll() {
|
||||
// TODO: Implement fetching all sales invoices
|
||||
return []
|
||||
async findAll(posInfo: IPosPayload, query: SalesInvoicesFilterDto = {}) {
|
||||
const page = query.page || 1
|
||||
const perPage = Math.min(query.perPage || 10, 100)
|
||||
|
||||
const where = this.buildFindAllWhere(posInfo, query)
|
||||
const [items, total] = await this.prisma.$transaction(async tx => [
|
||||
await tx.salesInvoice.findMany({
|
||||
where,
|
||||
orderBy: {
|
||||
created_at: 'desc',
|
||||
},
|
||||
skip: (page - 1) * perPage,
|
||||
take: perPage,
|
||||
select: {
|
||||
id: true,
|
||||
code: true,
|
||||
invoice_number: true,
|
||||
invoice_date: true,
|
||||
created_at: true,
|
||||
total_amount: true,
|
||||
customer: {
|
||||
select: {
|
||||
type: true,
|
||||
individual: {
|
||||
select: {
|
||||
first_name: true,
|
||||
last_name: true,
|
||||
mobile_number: true,
|
||||
national_id: true,
|
||||
},
|
||||
},
|
||||
legal: {
|
||||
select: {
|
||||
name: true,
|
||||
economic_code: true,
|
||||
registration_number: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fiscal: {
|
||||
select: {
|
||||
attempts: {
|
||||
orderBy: {
|
||||
created_at: 'desc',
|
||||
},
|
||||
take: 1,
|
||||
select: {
|
||||
status: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
await tx.salesInvoice.count({ where }),
|
||||
])
|
||||
|
||||
const summaryItems = items.map(invoice => ({
|
||||
...invoice,
|
||||
status: translateEnumValue(
|
||||
'FiscalResponseStatus',
|
||||
invoice.fiscal?.attempts?.[0]?.status || FiscalResponseStatus.NOT_SEND,
|
||||
),
|
||||
}))
|
||||
|
||||
return ResponseMapper.paginate(summaryItems, { total, page, perPage })
|
||||
}
|
||||
|
||||
findOne(id: number) {
|
||||
// TODO: Implement fetching a single sales invoice
|
||||
return {}
|
||||
async findOne(posInfo: IPosPayload, id: string) {
|
||||
const invoice = await this.prisma.salesInvoice.findFirstOrThrow({
|
||||
where: {
|
||||
id,
|
||||
pos: {
|
||||
complex: {
|
||||
business_activity_id: posInfo.business_id,
|
||||
},
|
||||
},
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
code: true,
|
||||
invoice_number: true,
|
||||
invoice_date: true,
|
||||
created_at: true,
|
||||
updated_at: true,
|
||||
notes: true,
|
||||
total_amount: true,
|
||||
unknown_customer: true,
|
||||
customer: {
|
||||
select: {
|
||||
id: true,
|
||||
type: true,
|
||||
individual: {
|
||||
select: {
|
||||
first_name: true,
|
||||
last_name: true,
|
||||
mobile_number: true,
|
||||
national_id: true,
|
||||
postal_code: true,
|
||||
economic_code: true,
|
||||
},
|
||||
},
|
||||
legal: {
|
||||
select: {
|
||||
name: true,
|
||||
economic_code: true,
|
||||
registration_number: true,
|
||||
postal_code: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
pos: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
complex: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
business_activity: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
consumer_account: {
|
||||
select: {
|
||||
id: true,
|
||||
role: true,
|
||||
account: {
|
||||
select: {
|
||||
username: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
items: {
|
||||
select: {
|
||||
id: true,
|
||||
good_id: true,
|
||||
service_id: true,
|
||||
quantity: true,
|
||||
measure_unit_code: true,
|
||||
measure_unit_text: true,
|
||||
sku_code: true,
|
||||
unit_price: true,
|
||||
discount: true,
|
||||
total_amount: true,
|
||||
notes: true,
|
||||
payload: true,
|
||||
good_snapshot: true,
|
||||
},
|
||||
},
|
||||
payments: {
|
||||
select: {
|
||||
id: true,
|
||||
amount: true,
|
||||
payment_method: true,
|
||||
paid_at: true,
|
||||
created_at: true,
|
||||
terminal_info: {
|
||||
select: {
|
||||
terminal_id: true,
|
||||
stan: true,
|
||||
rrn: true,
|
||||
transaction_date_time: true,
|
||||
customer_card_no: true,
|
||||
description: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
fiscal: {
|
||||
select: {
|
||||
id: true,
|
||||
retry_count: true,
|
||||
last_attempt_at: true,
|
||||
attempts: {
|
||||
orderBy: {
|
||||
created_at: 'desc',
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
attempt_no: true,
|
||||
status: true,
|
||||
tax_id: true,
|
||||
error_message: true,
|
||||
sent_at: true,
|
||||
received_at: true,
|
||||
created_at: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return ResponseMapper.single({
|
||||
...invoice,
|
||||
status: invoice.fiscal?.attempts?.[0]?.status || FiscalResponseStatus.NOT_SEND,
|
||||
})
|
||||
}
|
||||
|
||||
// async send(invoiceId: string, posInfo: IPosPayload) {
|
||||
@@ -83,7 +288,7 @@ export class SalesInvoicesService {
|
||||
// }
|
||||
|
||||
async create(data: CreateSalesInvoiceDto, posInfo: IPosPayload) {
|
||||
const { business_id, pos_id, consumer_account_id } = posInfo
|
||||
const { business_id, pos_id, consumer_account_id, complex_id } = posInfo
|
||||
const normalizedInvoiceDate = this.normalizeInvoiceDate(data.invoice_date)
|
||||
const { payments, terminalInfo } = this.buildPaymentsData(
|
||||
data.payments,
|
||||
@@ -101,6 +306,8 @@ export class SalesInvoicesService {
|
||||
normalizedInvoiceDate,
|
||||
invoiceNumber,
|
||||
consumer_account_id,
|
||||
businessId: business_id,
|
||||
complexId: complex_id,
|
||||
pos_id,
|
||||
goodsById,
|
||||
customerId: newCustomerId,
|
||||
@@ -156,6 +363,161 @@ export class SalesInvoicesService {
|
||||
)
|
||||
}
|
||||
|
||||
private buildFindAllWhere(posInfo: IPosPayload, query: SalesInvoicesFilterDto) {
|
||||
const where: Prisma.SalesInvoiceWhereInput = {
|
||||
pos: {
|
||||
complex: {
|
||||
business_activity_id: posInfo.business_id,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if (query.code?.trim()) {
|
||||
where.code = {
|
||||
contains: query.code.trim(),
|
||||
}
|
||||
}
|
||||
|
||||
if (query.invoice_date_from || query.invoice_date_to) {
|
||||
where.invoice_date = {
|
||||
...(query.invoice_date_from ? { gte: new Date(query.invoice_date_from) } : {}),
|
||||
...(query.invoice_date_to ? { lte: new Date(query.invoice_date_to) } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
if (query.created_at_from || query.created_at_to) {
|
||||
where.created_at = {
|
||||
...(query.created_at_from ? { gte: new Date(query.created_at_from) } : {}),
|
||||
...(query.created_at_to ? { lte: new Date(query.created_at_to) } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
query.total_amount !== undefined ||
|
||||
query.total_amount_from !== undefined ||
|
||||
query.total_amount_to !== undefined
|
||||
) {
|
||||
where.total_amount = {
|
||||
...(query.total_amount !== undefined ? { equals: query.total_amount } : {}),
|
||||
...(query.total_amount_from !== undefined
|
||||
? { gte: query.total_amount_from }
|
||||
: {}),
|
||||
...(query.total_amount_to !== undefined ? { lte: query.total_amount_to } : {}),
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
query.customer_name?.trim() ||
|
||||
query.customer_mobile?.trim() ||
|
||||
query.customer_national_id?.trim() ||
|
||||
query.customer_economic_code?.trim()
|
||||
) {
|
||||
where.customer = {
|
||||
is: {
|
||||
OR: [
|
||||
...(query.customer_name?.trim()
|
||||
? [
|
||||
{
|
||||
individual: {
|
||||
is: {
|
||||
OR: [
|
||||
{
|
||||
first_name: {
|
||||
contains: query.customer_name.trim(),
|
||||
},
|
||||
},
|
||||
{
|
||||
last_name: {
|
||||
contains: query.customer_name.trim(),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
legal: {
|
||||
is: {
|
||||
name: {
|
||||
contains: query.customer_name.trim(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(query.customer_mobile?.trim()
|
||||
? [
|
||||
{
|
||||
individual: {
|
||||
is: {
|
||||
mobile_number: {
|
||||
contains: query.customer_mobile.trim(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(query.customer_national_id?.trim()
|
||||
? [
|
||||
{
|
||||
individual: {
|
||||
is: {
|
||||
national_id: {
|
||||
contains: query.customer_national_id.trim(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(query.customer_economic_code?.trim()
|
||||
? [
|
||||
{
|
||||
legal: {
|
||||
is: {
|
||||
OR: [
|
||||
{
|
||||
economic_code: {
|
||||
contains: query.customer_economic_code.trim(),
|
||||
},
|
||||
},
|
||||
{
|
||||
registration_number: {
|
||||
contains: query.customer_economic_code.trim(),
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
if (query.status) {
|
||||
if (query.status === FiscalResponseStatus.NOT_SEND) {
|
||||
where.fiscal = null
|
||||
} else {
|
||||
where.fiscal = {
|
||||
is: {
|
||||
attempts: {
|
||||
some: {
|
||||
status: query.status,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return where
|
||||
}
|
||||
|
||||
private normalizeInvoiceDate(invoiceDate: Date | string) {
|
||||
return new Date(invoiceDate).toISOString() as any
|
||||
}
|
||||
@@ -339,11 +701,21 @@ export class SalesInvoicesService {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
sku: true,
|
||||
sku: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
local_sku: true,
|
||||
barcode: true,
|
||||
pricing_model: true,
|
||||
unit_type: true,
|
||||
measure_unit: {
|
||||
select: {
|
||||
id: true,
|
||||
name: true,
|
||||
},
|
||||
},
|
||||
base_sale_price: true,
|
||||
image_url: true,
|
||||
category: {
|
||||
@@ -364,6 +736,8 @@ export class SalesInvoicesService {
|
||||
normalizedInvoiceDate: Date
|
||||
invoiceNumber: number
|
||||
consumer_account_id: string
|
||||
businessId: string
|
||||
complexId: string
|
||||
pos_id: string
|
||||
goodsById: Map<string, any>
|
||||
customerId: string | null
|
||||
@@ -376,6 +750,8 @@ export class SalesInvoicesService {
|
||||
pos_id,
|
||||
goodsById,
|
||||
customerId,
|
||||
businessId,
|
||||
complexId,
|
||||
} = params
|
||||
const { customer_id, customer_type, customer, payments, ...invoiceData } = data
|
||||
|
||||
@@ -384,7 +760,7 @@ export class SalesInvoicesService {
|
||||
invoice_date: normalizedInvoiceDate,
|
||||
invoice_number: invoiceNumber,
|
||||
total_amount: data.total_amount,
|
||||
code: 'INV-' + Date.now(),
|
||||
code: this.generateInvoiceCode(businessId, complexId, pos_id, invoiceNumber),
|
||||
items: {
|
||||
createMany: {
|
||||
data: data.items.map(item => ({
|
||||
@@ -392,6 +768,7 @@ export class SalesInvoicesService {
|
||||
quantity: item.quantity,
|
||||
unit_price: item.unit_price,
|
||||
total_amount: item.total_amount,
|
||||
measure_unit_text: item.unit_type,
|
||||
payload: item.payload ? JSON.parse(JSON.stringify(item.payload)) : undefined,
|
||||
good_snapshot: item.good_id
|
||||
? JSON.parse(
|
||||
@@ -483,4 +860,13 @@ export class SalesInvoicesService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private generateInvoiceCode(
|
||||
business_id: string,
|
||||
complex_id: string,
|
||||
pos_id: string,
|
||||
invoice_number: number,
|
||||
) {
|
||||
return `${business_id.substring(0, 2)}-${complex_id.substring(0, 2)}-${pos_id.substring(0, 2)}-${invoice_number.toString()}`
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user