feat(inventories): add cardex retrieval for inventory and product
feat(pos): update sale invoice creation to include customer and item details feat(pos): enhance POS account service to include today's sales information feat(pos): refactor order DTO to create sale invoice with detailed item structure feat(products): add minimum stock alert level to product creation and update DTOs fix(triggers): implement stock validation and movement logging for sales invoice items refactor(database): update sales invoices schema to include posAccountId and remove inventoryId
This commit is contained in:
@@ -51,4 +51,9 @@ export class InventoriesController {
|
||||
getProductCardex(@Param('id') id: string, @Param('productId') productId: string) {
|
||||
return this.inventoriesService.getProductCardex(Number(id), Number(productId))
|
||||
}
|
||||
|
||||
@Get(':id/cardex')
|
||||
getCardex(@Param('id') id: string) {
|
||||
return this.inventoriesService.getCardex(Number(id))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,37 +161,46 @@ export class InventoriesService {
|
||||
return ResponseMapper.list(mapped)
|
||||
}
|
||||
|
||||
async getCardex(inventoryId: number) {
|
||||
const movements = await this.prisma.stockMovement.findMany({
|
||||
where: { inventoryId },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
include: {
|
||||
customer: { select: { id: true, firstName: true, lastName: true } },
|
||||
supplier: { select: { id: true, firstName: true, lastName: true } },
|
||||
counterInventory: {
|
||||
select: { id: true, name: true },
|
||||
},
|
||||
product: {
|
||||
select: { id: true, name: true, sku: true },
|
||||
},
|
||||
},
|
||||
omit: {
|
||||
inventoryId: true,
|
||||
productId: true,
|
||||
customerId: true,
|
||||
supplierId: true,
|
||||
counterInventoryId: true,
|
||||
},
|
||||
})
|
||||
|
||||
return ResponseMapper.list(movements)
|
||||
}
|
||||
|
||||
async getProductCardex(inventoryId: number, productId: number) {
|
||||
const movements = await this.prisma.stockMovement.findMany({
|
||||
where: { productId, inventoryId },
|
||||
orderBy: { createdAt: 'asc' },
|
||||
include: { inventory: true, supplier: true },
|
||||
include: {
|
||||
customer: { select: { id: true, firstName: true, lastName: true } },
|
||||
supplier: { select: { id: true, firstName: true, lastName: true } },
|
||||
counterInventory: {
|
||||
select: { id: true, name: true },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const mapped = movements.map(movement => ({
|
||||
id: movement.id,
|
||||
type: movement.type,
|
||||
quantity: Number(movement.quantity),
|
||||
fee: Number(movement.fee),
|
||||
totalCost: Number(movement.totalCost),
|
||||
avgCost: Number(movement.avgCost),
|
||||
referenceType: movement.referenceType,
|
||||
referenceId: movement.referenceId,
|
||||
createdAt: movement.createdAt,
|
||||
remainedInStock: movement.remainedInStock,
|
||||
inventory: {
|
||||
id: movement.inventory.id,
|
||||
name: movement.inventory.name,
|
||||
},
|
||||
supplier: movement.supplier
|
||||
? {
|
||||
id: movement.supplier.id,
|
||||
name: movement.supplier.firstName + ' ' + movement.supplier.lastName,
|
||||
}
|
||||
: null,
|
||||
}))
|
||||
|
||||
return ResponseMapper.list(mapped)
|
||||
return ResponseMapper.list(movements)
|
||||
}
|
||||
|
||||
async getInventoryBankAccounts(inventoryId: number) {
|
||||
|
||||
@@ -62,17 +62,39 @@ export class PosAccountsService {
|
||||
},
|
||||
},
|
||||
},
|
||||
salesInvoices: {
|
||||
where: {
|
||||
createdAt: { gte: new Date(new Date().setHours(0, 0, 0, 0)) },
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
code: true,
|
||||
totalAmount: true,
|
||||
createdAt: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
omit: {
|
||||
inventoryId: true,
|
||||
bankAccountId: true,
|
||||
},
|
||||
})
|
||||
|
||||
return ResponseMapper.list(
|
||||
items.map(item => ({
|
||||
...item,
|
||||
bankAccount: item.inventoryBankAccount?.bankAccount,
|
||||
})),
|
||||
items.map(item => {
|
||||
const { inventoryBankAccount, salesInvoices, ...rest } = item
|
||||
return {
|
||||
...rest,
|
||||
bankAccount: inventoryBankAccount?.bankAccount,
|
||||
todaySalesInfo: {
|
||||
salesCount: salesInvoices.length,
|
||||
salesTotal: salesInvoices.reduce(
|
||||
(acc, si) => acc + Number(si.totalAmount),
|
||||
0,
|
||||
),
|
||||
},
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,37 @@
|
||||
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'
|
||||
import { ArrayMinSize, IsNumber, IsOptional } from 'class-validator'
|
||||
import { Type } from 'class-transformer'
|
||||
import { ArrayMinSize, IsInt, IsNumber, IsOptional, IsString } from 'class-validator'
|
||||
|
||||
export class CreateOrderDto {
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
export class CreateSaleInvoiceDto {
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
customerId?: number
|
||||
|
||||
@ApiPropertyOptional()
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
description?: string
|
||||
|
||||
@ApiProperty()
|
||||
@ArrayMinSize(1)
|
||||
items: CreateOrderItemDto[]
|
||||
}
|
||||
|
||||
export class CreateOrderItemDto {
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
@IsInt()
|
||||
@Type(() => Number)
|
||||
productId: number
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
count: number
|
||||
|
||||
@ApiProperty()
|
||||
@IsNumber()
|
||||
@Type(() => Number)
|
||||
fee: number
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Body, Controller, Get, Param, Post, Query } from '@nestjs/common'
|
||||
import { CreateOrderDto } from './dto/create-pos.dto'
|
||||
import { CreateSaleInvoiceDto } from './dto/create-pos.dto'
|
||||
import { PosService } from './pos.service'
|
||||
|
||||
@Controller('pos')
|
||||
@@ -34,7 +34,7 @@ export class PosController {
|
||||
}
|
||||
|
||||
@Post('/:posId/orders/create')
|
||||
async createOrder(@Param('posId') posId: string, @Body() dto: CreateOrderDto) {
|
||||
async createOrder(@Param('posId') posId: string, @Body() dto: CreateSaleInvoiceDto) {
|
||||
return this.posService.createOrder(Number(posId), dto)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,29 +1,14 @@
|
||||
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 { CreateOrderDto } from './dto/create-pos.dto'
|
||||
import { CreateSaleInvoiceDto } from './dto/create-pos.dto'
|
||||
|
||||
@Injectable()
|
||||
export class PosService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
|
||||
async getDefaultInventoryId() {
|
||||
const inventory = await this.prisma.inventory.findFirst({
|
||||
where: { isPointOfSale: true },
|
||||
select: { id: true },
|
||||
})
|
||||
return inventory?.id || null
|
||||
}
|
||||
|
||||
async getDefaultPosId(inventoryId: number) {
|
||||
const pos = await this.prisma.posAccount.findFirst({
|
||||
where: { inventoryId },
|
||||
select: { id: true },
|
||||
})
|
||||
return pos?.id || null
|
||||
}
|
||||
|
||||
async getInfo(posId: number) {
|
||||
const info = await this.prisma.posAccount.findUniqueOrThrow({
|
||||
where: { id: posId },
|
||||
@@ -173,41 +158,34 @@ export class PosService {
|
||||
return ResponseMapper.list(categories)
|
||||
}
|
||||
|
||||
async createOrder(posId: number, data: CreateOrderDto) {
|
||||
const pos = await this.prisma.posAccount.findUniqueOrThrow({
|
||||
where: { id: posId },
|
||||
select: { inventoryId: true },
|
||||
})
|
||||
const inventoryId = pos.inventoryId
|
||||
|
||||
const { customerId, ...res } = data
|
||||
const preparedOrder = { ...res } as any
|
||||
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')
|
||||
|
||||
preparedOrder.code = String(Number(lastCode) + 1)
|
||||
const newCode = String(Number(lastCode) + 1)
|
||||
|
||||
preparedOrder.totalAmount = data.items.reduce(
|
||||
(acc, item) => acc + Number(item.fee),
|
||||
0,
|
||||
)
|
||||
const totalAmount = data.items.reduce((acc, item) => acc + item.count * item.fee, 0)
|
||||
|
||||
preparedOrder.inventory = { connect: { id: inventoryId } }
|
||||
preparedOrder.customer = { connect: { id: customerId } }
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(preparedOrder, 'items')) {
|
||||
preparedOrder.items = {
|
||||
create: preparedOrder.items.map((item: any) => ({
|
||||
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,
|
||||
fee: item.fee,
|
||||
total: item.count * item.fee,
|
||||
})),
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const item = await this.prisma.salesInvoice.create({ data: preparedOrder })
|
||||
|
||||
Reference in New Issue
Block a user