feat: implement POS order management system

- Added CreateOrderDto and CreateOrderItemDto for order creation.
- Developed PosOrdersController to handle order-related endpoints.
- Created PosOrdersService for business logic related to orders.
- Implemented PosOrdersWorkflow for order processing workflows.
- Established Prisma integration for database operations in orders.
- Introduced SalesInvoicesModule and related DTOs for sales invoice management.
- Enhanced SalesInvoicesService and SalesInvoicesWorkflow for invoice processing.
This commit is contained in:
2026-01-07 15:25:59 +03:30
parent b05048e62f
commit 5fd6611aca
37 changed files with 1974 additions and 291 deletions
+45 -88
View File
@@ -1,9 +1,6 @@
import { Injectable } from '@nestjs/common'
import { ResponseMapper } from '../../common/response/response-mapper'
import { Prisma } from '../../generated/prisma/client'
import { SalesInvoiceCreateInput } from '../../generated/prisma/models'
import { PrismaService } from '../../prisma/prisma.service'
import { CreateSaleInvoiceDto } from './dto/create-pos.dto'
@Injectable()
export class PosService {
@@ -53,67 +50,64 @@ export class PosService {
page?: number
pageSize?: number
q?: string
productIds?: number[]
} = {},
) {
const { isAvailable, page = 1, pageSize = 50, q } = options
const { isAvailable, page = 1, pageSize = 50, q, productIds } = options
const pos = await this.prisma.posAccount.findUniqueOrThrow({
where: { id: posId },
select: { inventoryId: true },
})
const inventoryId = pos.inventoryId
const query: Prisma.StockBalanceFindManyArgs = {
where: {
inventoryId,
quantity:
isAvailable === true
? { gt: 0 }
: isAvailable === false
? { lte: 0 }
: undefined,
...(q && {
product: {
OR: [{ name: { contains: q } }, { sku: { contains: q } }],
},
}),
},
orderBy: {
quantity: 'desc',
},
let sql = `
SELECT sb.productId, sb.quantity - COALESCE(SUM(sr.quantity), 0) as availableQuantity, p.id, p.name, p.sku, p.description, p.salePrice, p.categoryId
FROM Stock_Balance sb
LEFT JOIN Stock_Reservations sr ON sb.productId = sr.productId AND sb.inventoryId = sr.inventoryId
LEFT JOIN Products p ON sb.productId = p.id
WHERE sb.inventoryId = ?
`
const params: any[] = [inventoryId]
if (productIds && productIds.length > 0) {
sql += ` AND sb.productId IN (${productIds.map(() => '?').join(',')})`
params.push(...productIds)
}
const [items, count] = await this.prisma.$transaction([
this.prisma.stockBalance.findMany({
where: query.where,
include: {
product: {
select: {
id: true,
name: true,
sku: true,
salePrice: true,
category: {
select: { id: true, name: true },
},
},
},
},
orderBy: {
quantity: 'desc',
},
skip: (page - 1) * pageSize,
take: pageSize,
}),
this.prisma.stockBalance.count({ where: query.where }),
])
if (q) {
sql += ` AND (p.name LIKE ? OR p.sku LIKE ?)`
params.push(`%${q}%`, `%${q}%`)
}
const mapped = items.map(item => ({
id: item.id,
quantity: Number(item.quantity),
product: item.product,
sql += ` GROUP BY sb.productId`
if (isAvailable !== undefined) {
sql += isAvailable
? ` HAVING availableQuantity > 0`
: ` HAVING availableQuantity <= 0`
}
sql += ` ORDER BY sb.quantity DESC`
const items = await this.prisma.$queryRawUnsafe(sql, ...params)
const mapped = (items as any[]).map(item => ({
id: item.productId,
quantity: Number(item.availableQuantity),
product: {
id: item.id,
name: item.name,
sku: item.sku,
description: item.description,
salePrice: item.salePrice,
categoryId: item.categoryId,
},
}))
return ResponseMapper.paginate(mapped, count, page, pageSize)
const count = mapped.length
const paginated = mapped.slice((page - 1) * pageSize, page * pageSize)
return ResponseMapper.paginate(paginated, count, page, pageSize)
}
async getProductCategories(posId: number) {
@@ -157,41 +151,4 @@ export class PosService {
const categories = Array.from(map.values())
return ResponseMapper.list(categories)
}
async createOrder(posId: number, data: CreateSaleInvoiceDto) {
const { customerId, items, ...res } = data
const lastCode = await this.prisma.salesInvoice
.findFirst({
where: { posAccountId: posId },
orderBy: { code: 'desc' },
select: { code: true },
})
.then(si => si?.code || '0')
const newCode = String(Number(lastCode) + 1)
const totalAmount = data.items.reduce(
(acc, item) => acc + item.count * item.unitPrice,
0,
)
const preparedOrder: SalesInvoiceCreateInput = {
...res,
code: newCode,
posAccount: { connect: { id: posId } },
customer: customerId ? { connect: { id: customerId } } : undefined,
totalAmount: totalAmount,
items: {
create: items.map(item => ({
product: { connect: { id: Number(item.productId) } },
count: item.count,
unitPrice: item.unitPrice,
total: item.count * item.unitPrice,
})),
},
}
const item = await this.prisma.salesInvoice.create({ data: preparedOrder })
return ResponseMapper.create(item)
}
}