change source of account id in prepared token to domain account id, create consumer salesInvoiceModule
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
dist
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.*.local
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
README.md
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
|
.DS_Store
|
||||||
|
*.log
|
||||||
|
coverage
|
||||||
|
.nyc_output
|
||||||
|
.jest-cache
|
||||||
|
prisma/dev.db
|
||||||
|
prisma/dev.db-journal
|
||||||
|
.turbo
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
build
|
||||||
|
dist
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Server Configuration
|
||||||
|
NODE_ENV=development
|
||||||
|
PORT=5002
|
||||||
|
|
||||||
|
# Database Configuration
|
||||||
|
DB_HOST=localhost
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_NAME=consumer_db
|
||||||
|
DB_USER=consumer_user
|
||||||
|
DB_PASSWORD=consumer_password
|
||||||
|
DB_ROOT_PASSWORD=root_password
|
||||||
|
DATABASE_URL=mysql://consumer_user:consumer_password@localhost:3306/consumer_db
|
||||||
|
SHADOW_DATABASE_URL=mysql://consumer_user:consumer_password@localhost:3306/consumer_db_shadow
|
||||||
|
|
||||||
|
# JWT Configuration
|
||||||
|
JWT_SECRET=your_super_secret_jwt_key_change_this_in_production
|
||||||
|
JWT_EXPIRATION=604800
|
||||||
|
|
||||||
|
# CORS Configuration
|
||||||
|
CORS_ORIGINS=http://localhost:5000,http://127.0.0.1:5000
|
||||||
|
|
||||||
|
# File Upload Configuration
|
||||||
|
UPLOAD_PATH=./uploads
|
||||||
|
MAX_FILE_SIZE=10485760
|
||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
# Build stage
|
||||||
|
FROM node:20-alpine AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install pnpm
|
||||||
|
RUN npm install -g pnpm
|
||||||
|
|
||||||
|
# Copy dependency files
|
||||||
|
COPY pnpm-lock.yaml package.json ./
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
RUN pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
# Copy source code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Generate Prisma client and build the application
|
||||||
|
RUN pnpm exec prisma generate
|
||||||
|
RUN pnpm run build
|
||||||
|
|
||||||
|
# Production stage
|
||||||
|
FROM node:20-alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install pnpm
|
||||||
|
RUN npm install -g pnpm
|
||||||
|
|
||||||
|
# Install dumb-init for proper signal handling
|
||||||
|
RUN apk add --no-cache dumb-init
|
||||||
|
|
||||||
|
# Copy package files
|
||||||
|
COPY package.json pnpm-lock.yaml ./
|
||||||
|
|
||||||
|
# Install only production dependencies
|
||||||
|
RUN pnpm install --frozen-lockfile --prod
|
||||||
|
|
||||||
|
# Copy built application from builder
|
||||||
|
COPY --from=builder /app/dist ./dist
|
||||||
|
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma
|
||||||
|
COPY prisma ./prisma
|
||||||
|
|
||||||
|
# Create non-root user
|
||||||
|
RUN addgroup -g 1001 -S nodejs && \
|
||||||
|
adduser -S nodejs -u 1001
|
||||||
|
|
||||||
|
USER nodejs
|
||||||
|
|
||||||
|
# Expose port
|
||||||
|
EXPOSE 5002
|
||||||
|
|
||||||
|
# Health check
|
||||||
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
|
CMD node -e "require('http').get('http://localhost:5002/api/v1/health', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})"
|
||||||
|
|
||||||
|
# Use dumb-init to handle signals properly
|
||||||
|
ENTRYPOINT ["dumb-init", "--"]
|
||||||
|
|
||||||
|
# Start application
|
||||||
|
CMD ["node", "dist/main.js"]
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
version: "3.9"
|
||||||
|
|
||||||
|
services:
|
||||||
|
database:
|
||||||
|
image: mariadb:11.4-jammy
|
||||||
|
container_name: consumer_api_db
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-root_password}
|
||||||
|
MARIADB_DATABASE: ${DB_NAME:-consumer_db}
|
||||||
|
MARIADB_USER: ${DB_USER:-consumer_user}
|
||||||
|
MARIADB_PASSWORD: ${DB_PASSWORD:-consumer_password}
|
||||||
|
MARIADB_ALLOW_EMPTY_ROOT_PASSWORD: "false"
|
||||||
|
ports:
|
||||||
|
- "${DB_PORT:-3306}:3306"
|
||||||
|
volumes:
|
||||||
|
- mariadb_data:/var/lib/mysql
|
||||||
|
- ./prisma/migrations:/docker-entrypoint-initdb.d
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
networks:
|
||||||
|
- consumer_network
|
||||||
|
|
||||||
|
api:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: consumer_api
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
database:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
NODE_ENV: ${NODE_ENV:-production}
|
||||||
|
PORT: ${PORT:-5002}
|
||||||
|
DATABASE_URL: mysql://${DB_USER:-consumer_user}:${DB_PASSWORD:-consumer_password}@database:3306/${DB_NAME:-consumer_db}
|
||||||
|
SHADOW_DATABASE_URL: mysql://${DB_USER:-consumer_user}:${DB_PASSWORD:-consumer_password}@database:3306/${DB_NAME:-consumer_db}_shadow
|
||||||
|
JWT_SECRET: ${JWT_SECRET:-your_jwt_secret_key_change_in_production}
|
||||||
|
JWT_EXPIRATION: ${JWT_EXPIRATION:-604800}
|
||||||
|
CORS_ORIGINS: ${CORS_ORIGINS:-http://localhost:5000,http://127.0.0.1:5000}
|
||||||
|
ports:
|
||||||
|
- "${PORT:-5002}:5002"
|
||||||
|
volumes:
|
||||||
|
- ./src:/app/src
|
||||||
|
- /app/dist
|
||||||
|
- /app/node_modules
|
||||||
|
networks:
|
||||||
|
- consumer_network
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
"CMD",
|
||||||
|
"wget",
|
||||||
|
"--quiet",
|
||||||
|
"--tries=1",
|
||||||
|
"--spider",
|
||||||
|
"http://localhost:5002/api/v1/health",
|
||||||
|
]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
mariadb_data:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
networks:
|
||||||
|
consumer_network:
|
||||||
|
driver: bridge
|
||||||
@@ -60,7 +60,7 @@ export class PosGuard implements CanActivate {
|
|||||||
user: {
|
user: {
|
||||||
accounts: {
|
accounts: {
|
||||||
some: {
|
some: {
|
||||||
account_id: account.account_id,
|
id: account.account_id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -81,20 +81,16 @@ export class PosGuard implements CanActivate {
|
|||||||
throw new ForbiddenException('شما دسترسی لازم را ندارید.')
|
throw new ForbiddenException('شما دسترسی لازم را ندارید.')
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundedAccount = await this.prisma.account.findUnique({
|
const foundedAccount = await this.prisma.consumerAccount.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id: account.account_id,
|
id: account.account_id,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
consumer_account: {
|
role: true,
|
||||||
select: {
|
|
||||||
role: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (foundedAccount?.consumer_account?.role === 'OWNER') {
|
if (foundedAccount?.role === 'OWNER') {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ export class AuthUtils {
|
|||||||
id: accountId,
|
id: accountId,
|
||||||
},
|
},
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
|
||||||
admin_account: {
|
admin_account: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -58,10 +57,16 @@ export class AuthUtils {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!adminAccount) throw new NotFoundException('متاسفانه کاربر شما یافت نشد')
|
if (!adminAccount || !adminAccount.admin_account)
|
||||||
|
throw new NotFoundException('متاسفانه کاربر شما یافت نشد')
|
||||||
|
|
||||||
|
const { user, id } = adminAccount.admin_account
|
||||||
return {
|
return {
|
||||||
...adminAccount.admin_account?.user,
|
account_id: id,
|
||||||
fullname: `${adminAccount.admin_account?.user.first_name} ${adminAccount.admin_account?.user.last_name}`,
|
user: {
|
||||||
|
...user,
|
||||||
|
fullname: `${user.first_name} ${user.last_name}`,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +78,7 @@ export class AuthUtils {
|
|||||||
select: {
|
select: {
|
||||||
consumer_account: {
|
consumer_account: {
|
||||||
select: {
|
select: {
|
||||||
|
id: true,
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -86,8 +92,17 @@ export class AuthUtils {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!consumerAccount) throw new NotFoundException('متاسفانه کاربر شما یافت نشد')
|
if (!consumerAccount || !consumerAccount.consumer_account)
|
||||||
return consumerAccount.consumer_account?.user
|
throw new NotFoundException('متاسفانه کاربر شما یافت نشد')
|
||||||
|
|
||||||
|
const { user, id } = consumerAccount.consumer_account
|
||||||
|
return {
|
||||||
|
account_id: id,
|
||||||
|
user: {
|
||||||
|
...user,
|
||||||
|
fullname: `${user.first_name} ${user.last_name}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProviderAccountInfo(accountId: string) {
|
async getProviderAccountInfo(accountId: string) {
|
||||||
@@ -98,6 +113,7 @@ export class AuthUtils {
|
|||||||
select: {
|
select: {
|
||||||
provider_account: {
|
provider_account: {
|
||||||
select: {
|
select: {
|
||||||
|
id: true,
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -111,8 +127,17 @@ export class AuthUtils {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!providerAccount) throw new NotFoundException('متاسفانه کاربر شما یافت نشد')
|
if (!providerAccount || !providerAccount.provider_account)
|
||||||
return providerAccount.provider_account?.user
|
throw new NotFoundException('متاسفانه کاربر شما یافت نشد')
|
||||||
|
|
||||||
|
const { user, id } = providerAccount.provider_account
|
||||||
|
return {
|
||||||
|
account_id: id,
|
||||||
|
user: {
|
||||||
|
...user,
|
||||||
|
fullname: user.name,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPartnerAccountInfo(accountId: string) {
|
async getPartnerAccountInfo(accountId: string) {
|
||||||
@@ -123,6 +148,7 @@ export class AuthUtils {
|
|||||||
select: {
|
select: {
|
||||||
partner_account: {
|
partner_account: {
|
||||||
select: {
|
select: {
|
||||||
|
id: true,
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
@@ -136,33 +162,50 @@ export class AuthUtils {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!partnerAccount) throw new NotFoundException('متاسفانه کاربر شما یافت نشد')
|
if (!partnerAccount || !partnerAccount.partner_account)
|
||||||
return partnerAccount.partner_account?.user
|
throw new NotFoundException('متاسفانه کاربر شما یافت نشد')
|
||||||
|
|
||||||
|
const { user, id } = partnerAccount.partner_account
|
||||||
|
return {
|
||||||
|
account_id: id,
|
||||||
|
user: {
|
||||||
|
...user,
|
||||||
|
fullname: user.name,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async generateLoginResponse(account: Account) {
|
async generateLoginResponse(account: Account) {
|
||||||
let preparedAccount = {
|
let preparedAccount = {
|
||||||
type: account.type,
|
type: account.type,
|
||||||
username: account.username,
|
username: account.username,
|
||||||
account_id: account.id,
|
account_id: '',
|
||||||
user: {},
|
user: {},
|
||||||
} as AccessTokenPayload
|
} as AccessTokenPayload
|
||||||
|
|
||||||
|
let accountInfo = {
|
||||||
|
user: {},
|
||||||
|
account_id: '',
|
||||||
|
}
|
||||||
|
|
||||||
switch (account.type) {
|
switch (account.type) {
|
||||||
case AccountType.ADMIN:
|
case AccountType.ADMIN:
|
||||||
preparedAccount.user = await this.getAdminAccountInfo(account.id)
|
accountInfo = await this.getAdminAccountInfo(account.id)
|
||||||
break
|
break
|
||||||
case AccountType.CONSUMER:
|
case AccountType.CONSUMER:
|
||||||
preparedAccount.user = await this.getConsumerAccountInfo(account.id)
|
accountInfo = await this.getConsumerAccountInfo(account.id)
|
||||||
break
|
break
|
||||||
case AccountType.PARTNER:
|
case AccountType.PARTNER:
|
||||||
preparedAccount.user = await this.getPartnerAccountInfo(account.id)
|
accountInfo = await this.getPartnerAccountInfo(account.id)
|
||||||
break
|
break
|
||||||
case AccountType.PROVIDER:
|
case AccountType.PROVIDER:
|
||||||
preparedAccount.user = await this.getProviderAccountInfo(account.id)
|
accountInfo = await this.getProviderAccountInfo(account.id)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preparedAccount.account_id = accountInfo.account_id
|
||||||
|
preparedAccount.user = accountInfo.user
|
||||||
|
|
||||||
if (!preparedAccount.user) {
|
if (!preparedAccount.user) {
|
||||||
throw new NotFoundException('متاسفانه کاربر شما یافت نشد')
|
throw new NotFoundException('متاسفانه کاربر شما یافت نشد')
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import { PrismaModule } from '@/prisma/prisma.module'
|
|||||||
import { Module } from '@nestjs/common'
|
import { Module } from '@nestjs/common'
|
||||||
import { ComplexPosesController } from './poses.controller'
|
import { ComplexPosesController } from './poses.controller'
|
||||||
import { ComplexPosesService } from './poses.service'
|
import { ComplexPosesService } from './poses.service'
|
||||||
|
import { ConsumerPosSalesInvoicesModule } from './sales-invoices/sales-invoices.module'
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrismaModule],
|
imports: [PrismaModule, ConsumerPosSalesInvoicesModule],
|
||||||
controllers: [ComplexPosesController],
|
controllers: [ComplexPosesController],
|
||||||
providers: [ComplexPosesService],
|
providers: [ComplexPosesService],
|
||||||
exports: [ComplexPosesService],
|
exports: [ComplexPosesService],
|
||||||
|
|||||||
+66
@@ -0,0 +1,66 @@
|
|||||||
|
import { ApiProperty, PartialType } from '@nestjs/swagger'
|
||||||
|
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 {
|
||||||
|
// @TODO: totalAmount must calculated instead of get from api
|
||||||
|
@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({ required: false })
|
||||||
|
@IsOptional()
|
||||||
|
@IsObject()
|
||||||
|
customer?:
|
||||||
|
| {
|
||||||
|
first_name: string
|
||||||
|
last_name: string
|
||||||
|
}
|
||||||
|
| CustomerIndividual
|
||||||
|
| CustomerLegal
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateSalesInvoiceDto extends PartialType(CreateSalesInvoiceDto) {}
|
||||||
+57
@@ -0,0 +1,57 @@
|
|||||||
|
import { ApiProperty, PartialType } from '@nestjs/swagger'
|
||||||
|
import { IsEnum, IsNumber, IsObject, IsOptional, IsString } from 'class-validator'
|
||||||
|
import type { SaleInvoiceType } from 'common/interfaces/sale-invoice-payload'
|
||||||
|
import { UnitType } from 'generated/prisma/enums'
|
||||||
|
|
||||||
|
export class CreateSalesInvoiceItemDto {
|
||||||
|
@IsNumber()
|
||||||
|
@ApiProperty({ required: true })
|
||||||
|
unit_price: number
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@ApiProperty({ required: true, default: 1 })
|
||||||
|
quantity: number
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@ApiProperty({ required: true })
|
||||||
|
total_amount: number
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
@ApiProperty({ required: true })
|
||||||
|
discount_amount: number
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
@ApiProperty({ required: true })
|
||||||
|
invoice_id: string
|
||||||
|
|
||||||
|
@IsEnum(UnitType)
|
||||||
|
@ApiProperty({ enum: Object.values(UnitType) })
|
||||||
|
unit_type: UnitType
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@ApiProperty({ required: false })
|
||||||
|
good_id?: string
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@ApiProperty({ required: false })
|
||||||
|
service_id?: string
|
||||||
|
|
||||||
|
// @IsEnum(SalesInvoiceItemPricingModel)
|
||||||
|
// @ApiProperty({ enum: Object.values(SalesInvoiceItemPricingModel) })
|
||||||
|
// @IsOptional()
|
||||||
|
// pricingModel: SalesInvoiceItemPricingModel
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsObject()
|
||||||
|
@ApiProperty({ required: false, default: {} })
|
||||||
|
payload: SaleInvoiceType
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
@ApiProperty({ required: false, default: '' })
|
||||||
|
notes: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateSalesInvoiceItemDto extends PartialType(CreateSalesInvoiceItemDto) {}
|
||||||
+22
@@ -0,0 +1,22 @@
|
|||||||
|
import { Body, Controller, Get, Param, Post } from '@nestjs/common'
|
||||||
|
import { SalesInvoiceItemsService } from './sales-invoice-items.service'
|
||||||
|
|
||||||
|
@Controller('sales_invoice_items')
|
||||||
|
export class SalesInvoiceItemsController {
|
||||||
|
constructor(private readonly salesInvoiceItemsService: SalesInvoiceItemsService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
findAll() {
|
||||||
|
return this.salesInvoiceItemsService.findAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
findOne(@Param('id') id: string) {
|
||||||
|
return this.salesInvoiceItemsService.findOne(+id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
create(@Body() data: any) {
|
||||||
|
return this.salesInvoiceItemsService.create(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from '@nestjs/common'
|
||||||
|
import { SalesInvoiceItemsController } from './sales-invoice-items.controller'
|
||||||
|
import { SalesInvoiceItemsService } from './sales-invoice-items.service'
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [SalesInvoiceItemsController],
|
||||||
|
providers: [SalesInvoiceItemsService],
|
||||||
|
})
|
||||||
|
export class SalesInvoiceItemsModule {}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
import { Injectable } from '@nestjs/common'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SalesInvoiceItemsService {
|
||||||
|
findAll() {
|
||||||
|
// TODO: Implement fetching all sales invoice items
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
findOne(id: number) {
|
||||||
|
// TODO: Implement fetching a single sales invoice item
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
create(data: any) {
|
||||||
|
// TODO: Implement sales invoice item creation
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
import { ApiProperty, PartialType } from '@nestjs/swagger'
|
||||||
|
import { IsDateString, IsEnum, IsNumber, IsString } from 'class-validator'
|
||||||
|
|
||||||
|
enum PaymentMethodType {
|
||||||
|
CASH = 'CASH',
|
||||||
|
CARD = 'CARD',
|
||||||
|
BANK = 'BANK',
|
||||||
|
CHECK = 'CHECK',
|
||||||
|
OTHER = 'OTHER',
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CreateSalesInvoicePaymentDto {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
invoice_id: string
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNumber()
|
||||||
|
amount: number
|
||||||
|
|
||||||
|
@ApiProperty({ enum: PaymentMethodType })
|
||||||
|
@IsEnum(PaymentMethodType)
|
||||||
|
payment_method: PaymentMethodType
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsDateString()
|
||||||
|
paid_at: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UpdateSalesInvoicePaymentDto extends PartialType(
|
||||||
|
CreateSalesInvoicePaymentDto,
|
||||||
|
) {}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
import { Body, Controller, Get, Param, Post } from '@nestjs/common'
|
||||||
|
import { SalesInvoicePaymentsService } from './sales-invoice-payments.service'
|
||||||
|
|
||||||
|
@Controller('sales-invoice-payments')
|
||||||
|
export class SalesInvoicePaymentsController {
|
||||||
|
constructor(
|
||||||
|
private readonly salesInvoicePaymentsService: SalesInvoicePaymentsService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
findAll() {
|
||||||
|
return this.salesInvoicePaymentsService.findAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
findOne(@Param('id') id: string) {
|
||||||
|
return this.salesInvoicePaymentsService.findOne(+id)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
create(@Body() data: any) {
|
||||||
|
return this.salesInvoicePaymentsService.create(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from '@nestjs/common'
|
||||||
|
import { SalesInvoicePaymentsController } from './sales-invoice-payments.controller'
|
||||||
|
import { SalesInvoicePaymentsService } from './sales-invoice-payments.service'
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [SalesInvoicePaymentsController],
|
||||||
|
providers: [SalesInvoicePaymentsService],
|
||||||
|
})
|
||||||
|
export class SalesInvoicePaymentsModule {}
|
||||||
+19
@@ -0,0 +1,19 @@
|
|||||||
|
import { Injectable } from '@nestjs/common'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SalesInvoicePaymentsService {
|
||||||
|
findAll() {
|
||||||
|
// TODO: Implement fetching all sales invoice payments
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
findOne(id: number) {
|
||||||
|
// TODO: Implement fetching a single sales invoice payment
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
|
||||||
|
create(data: any) {
|
||||||
|
// TODO: Implement sales invoice payment creation
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
+29
@@ -0,0 +1,29 @@
|
|||||||
|
import { Controller, Get, Param } from '@nestjs/common'
|
||||||
|
|
||||||
|
import { TokenAccount } from '@/common/decorators/tokenInfo.decorator'
|
||||||
|
import { ApiTags } from '@nestjs/swagger'
|
||||||
|
import { SalesInvoicesService } from './sales-invoices.service'
|
||||||
|
|
||||||
|
@ApiTags('ConsumerComplexPoses')
|
||||||
|
@Controller('consumer/complexes/:complexId/poses/:posId/sales_invoices')
|
||||||
|
export class SalesInvoicesController {
|
||||||
|
constructor(private readonly salesInvoicesService: SalesInvoicesService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
findAll(
|
||||||
|
@TokenAccount('userId') userId: string,
|
||||||
|
@Param('complexId') complexId: string,
|
||||||
|
@Param('posId') posId: string,
|
||||||
|
) {
|
||||||
|
return this.salesInvoicesService.findAll(userId, complexId, posId)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
findOne(
|
||||||
|
@Param('complexId') complexId: string,
|
||||||
|
@Param('posId') posId: string,
|
||||||
|
@Param('id') id: string,
|
||||||
|
) {
|
||||||
|
return this.salesInvoicesService.findOne(complexId, posId, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from '@nestjs/common'
|
||||||
|
import { SalesInvoicesController } from './sales-invoices.controller'
|
||||||
|
import { SalesInvoicesService } from './sales-invoices.service'
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [SalesInvoicesController],
|
||||||
|
providers: [SalesInvoicesService],
|
||||||
|
})
|
||||||
|
export class ConsumerPosSalesInvoicesModule {}
|
||||||
+108
@@ -0,0 +1,108 @@
|
|||||||
|
import { ResponseMapper } from '@/common/response/response-mapper'
|
||||||
|
import { SalesInvoiceWhereInput } from '@/generated/prisma/models'
|
||||||
|
import { PrismaService } from '@/prisma/prisma.service'
|
||||||
|
import { Injectable } from '@nestjs/common'
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SalesInvoicesService {
|
||||||
|
constructor(private prisma: PrismaService) {}
|
||||||
|
|
||||||
|
async findAll(user_id: string, complex_id: string, pos_id: string) {
|
||||||
|
const defaultWhere: SalesInvoiceWhereInput = {
|
||||||
|
pos_id,
|
||||||
|
pos: {
|
||||||
|
complex: {
|
||||||
|
id: complex_id,
|
||||||
|
business_activity: {
|
||||||
|
user_id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const pageSize = 10
|
||||||
|
const page = 1
|
||||||
|
|
||||||
|
const [items, count] = await this.prisma.$transaction([
|
||||||
|
this.prisma.salesInvoice.findMany({
|
||||||
|
where: defaultWhere,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
code: true,
|
||||||
|
invoice_date: true,
|
||||||
|
notes: true,
|
||||||
|
total_amount: true,
|
||||||
|
|
||||||
|
items: {
|
||||||
|
select: {
|
||||||
|
unit_type: true,
|
||||||
|
discount: true,
|
||||||
|
notes: true,
|
||||||
|
quantity: true,
|
||||||
|
total_amount: true,
|
||||||
|
unit_price: true,
|
||||||
|
payload: true,
|
||||||
|
good: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
sku: true,
|
||||||
|
barcode: true,
|
||||||
|
local_sku: true,
|
||||||
|
pricing_model: true,
|
||||||
|
unit_type: true,
|
||||||
|
category: {
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
payments: {
|
||||||
|
select: {
|
||||||
|
amount: true,
|
||||||
|
paid_at: true,
|
||||||
|
payment_method: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
customer: {
|
||||||
|
select: {
|
||||||
|
type: true,
|
||||||
|
customer_individuals: {
|
||||||
|
select: {
|
||||||
|
economic_code: true,
|
||||||
|
first_name: true,
|
||||||
|
last_name: true,
|
||||||
|
postal_code: true,
|
||||||
|
national_id: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
customer_legals: {
|
||||||
|
select: {
|
||||||
|
economic_code: true,
|
||||||
|
postal_code: true,
|
||||||
|
registration_number: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
unknown_customer: true,
|
||||||
|
},
|
||||||
|
skip: (page - 1) * pageSize,
|
||||||
|
take: pageSize,
|
||||||
|
}),
|
||||||
|
this.prisma.salesInvoice.count({
|
||||||
|
where: defaultWhere,
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
return ResponseMapper.paginate(items, { count, page, pageSize })
|
||||||
|
}
|
||||||
|
|
||||||
|
findOne(complex_id: string, pos_id: string, id: string) {
|
||||||
|
// TODO: Implement fetching a single sales invoice
|
||||||
|
return {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,7 +27,7 @@ export class PosMiddleware implements NestMiddleware {
|
|||||||
|
|
||||||
const consumerAccount = await this.prisma.consumerAccount.findUnique({
|
const consumerAccount = await this.prisma.consumerAccount.findUnique({
|
||||||
where: {
|
where: {
|
||||||
account_id: tokenAccount.account_id,
|
id: tokenAccount.account_id,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (!consumerAccount) {
|
if (!consumerAccount) {
|
||||||
|
|||||||
@@ -54,13 +54,14 @@ export class CreateSalesInvoiceDto {
|
|||||||
@ApiProperty({ required: false })
|
@ApiProperty({ required: false })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@IsObject()
|
@IsObject()
|
||||||
customer?:
|
customer?: {
|
||||||
| {
|
customer_individual?: Omit<CustomerIndividual, 'complex_id' | 'customer_id'>
|
||||||
first_name: string
|
customer_legal?: Omit<CustomerLegal, 'complex_id' | 'customer_id'>
|
||||||
last_name: string
|
customer_unknown?: {
|
||||||
}
|
first_name: string
|
||||||
| CustomerIndividual
|
last_name: string
|
||||||
| CustomerLegal
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateSalesInvoiceDto extends PartialType(CreateSalesInvoiceDto) {}
|
export class UpdateSalesInvoiceDto extends PartialType(CreateSalesInvoiceDto) {}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { IPosPayload } from '@/common/models/posPayload.model'
|
import { IPosPayload } from '@/common/models/posPayload.model'
|
||||||
|
import { SalesInvoiceCreateInput } from '@/generated/prisma/models'
|
||||||
import { PrismaService } from '@/prisma/prisma.service'
|
import { PrismaService } from '@/prisma/prisma.service'
|
||||||
import { Injectable } from '@nestjs/common'
|
import { BadRequestException, Injectable } from '@nestjs/common'
|
||||||
import { ResponseMapper } from 'common/response/response-mapper'
|
import { ResponseMapper } from 'common/response/response-mapper'
|
||||||
import type { CustomerIndividual, CustomerLegal } from 'generated/prisma/client'
|
import type { CustomerIndividual, CustomerLegal } from 'generated/prisma/client'
|
||||||
import { CustomerType, PaymentMethodType } from 'generated/prisma/enums'
|
import { CustomerType, PaymentMethodType } from 'generated/prisma/enums'
|
||||||
@@ -65,138 +66,128 @@ export class SalesInvoicesService {
|
|||||||
if (customer_id) {
|
if (customer_id) {
|
||||||
} else if (
|
} else if (
|
||||||
customer_type === CustomerType.INDIVIDUAL &&
|
customer_type === CustomerType.INDIVIDUAL &&
|
||||||
isCustomerIndividual(customer)
|
customer?.customer_individual
|
||||||
) {
|
) {
|
||||||
let customerIndividual = await $tx.customerIndividual.findUnique({
|
const customer_individual = customer.customer_individual
|
||||||
|
const customerIndividual = await $tx.customerIndividual.upsert({
|
||||||
where: {
|
where: {
|
||||||
complex_id_national_id: {
|
complex_id_national_id: {
|
||||||
complex_id,
|
complex_id,
|
||||||
national_id: customer.national_id,
|
national_id: customer_individual.national_id,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
create: {
|
||||||
|
...customer_individual,
|
||||||
|
customer: {
|
||||||
|
create: {
|
||||||
|
type: CustomerType.INDIVIDUAL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
complex: {
|
||||||
|
connect: {
|
||||||
|
id: complex_id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {},
|
||||||
|
select: {
|
||||||
|
customer_id: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (customerIndividual) {
|
if (!customerIndividual) {
|
||||||
await $tx.customerIndividual.update({
|
throw new BadRequestException('متاسفانه مشکلی در اطلاعات مشتری وجود دارد.')
|
||||||
where: {
|
|
||||||
complex_id_national_id: {
|
|
||||||
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,
|
|
||||||
customer_individuals: {
|
|
||||||
connect: {
|
|
||||||
customer_id: customerIndividual.customer_id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (createdCustomer && createdCustomer.id) {
|
|
||||||
newCustomerId = createdCustomer.id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (data.customer_type === CustomerType.LEGAL && isCustomerLegal(customer)) {
|
newCustomerId = customerIndividual.customer_id
|
||||||
let customerLegal = await $tx.customerLegal.findUnique({
|
} else if (data.customer_type === CustomerType.LEGAL && customer?.customer_legal) {
|
||||||
|
const customer_legal = customer.customer_legal
|
||||||
|
|
||||||
|
const customerLegal = await $tx.customerLegal.upsert({
|
||||||
where: {
|
where: {
|
||||||
complex_id_registration_number: {
|
complex_id_registration_number: {
|
||||||
complex_id,
|
complex_id,
|
||||||
registration_number: customer.registration_number,
|
registration_number: customer_legal.registration_number,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
create: {
|
||||||
|
...customer_legal,
|
||||||
|
customer: {
|
||||||
|
create: {
|
||||||
|
type: CustomerType.LEGAL,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
complex: {
|
||||||
|
connect: {
|
||||||
|
id: complex_id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
update: {},
|
||||||
|
select: {
|
||||||
|
customer_id: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
if (customerLegal) {
|
if (!customerLegal) {
|
||||||
await $tx.customerLegal.update({
|
throw new BadRequestException('متاسفانه مشکلی در اطلاعات مشتری وجود دارد.')
|
||||||
where: {
|
|
||||||
complex_id_registration_number: {
|
|
||||||
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,
|
|
||||||
|
|
||||||
customer_individuals: {
|
|
||||||
connect: {
|
|
||||||
customer_id: customerLegal.customer_id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
if (createdCustomer && createdCustomer.id) {
|
|
||||||
newCustomerId = createdCustomer.id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
newCustomerId = customerLegal.customer_id
|
||||||
} else if (data.customer_type === CustomerType.UNKNOWN) {
|
} else if (data.customer_type === CustomerType.UNKNOWN) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const salesInvoice = await $tx.salesInvoice.create({
|
const salesInvoiceData: SalesInvoiceCreateInput = {
|
||||||
data: {
|
...invoiceData,
|
||||||
...invoiceData,
|
total_amount: data.total_amount,
|
||||||
total_amount: data.total_amount,
|
code: 'INV-' + Date.now(),
|
||||||
code: 'INV-' + Date.now(),
|
|
||||||
|
|
||||||
items: {
|
items: {
|
||||||
createMany: {
|
createMany: {
|
||||||
data: data.items.map(item => ({
|
data: data.items.map(item => ({
|
||||||
good_id: item.good_id,
|
good_id: item.good_id,
|
||||||
quantity: item.quantity,
|
quantity: item.quantity,
|
||||||
unit_price: item.unit_price,
|
unit_price: item.unit_price,
|
||||||
total_amount: item.total_amount,
|
total_amount: item.total_amount,
|
||||||
payload: JSON.parse(JSON.stringify(item.payload)),
|
payload: item.payload
|
||||||
unit_type: item.unit_type,
|
? JSON.parse(JSON.stringify(item.payload))
|
||||||
// pricing_model: item.pricingModel,
|
: undefined,
|
||||||
})),
|
unit_type: item.unit_type,
|
||||||
},
|
// pricing_model: item.pricingModel,
|
||||||
},
|
})),
|
||||||
unknown_customer: {},
|
|
||||||
payments: {
|
|
||||||
createMany: {
|
|
||||||
data: payments,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
// customer: {
|
|
||||||
// connect: {
|
|
||||||
// id: newCustomerId,
|
|
||||||
// },
|
|
||||||
// },
|
|
||||||
account: {
|
|
||||||
connect: {
|
|
||||||
id: consumer_account_id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
pos: {
|
|
||||||
connect: {
|
|
||||||
id: pos_id,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
unknown_customer: {},
|
||||||
|
payments: {
|
||||||
|
createMany: {
|
||||||
|
data: payments,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// customer: {
|
||||||
|
// connect: {
|
||||||
|
// id: newCustomerId || undefined,
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
account: {
|
||||||
|
connect: {
|
||||||
|
id: consumer_account_id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
pos: {
|
||||||
|
connect: {
|
||||||
|
id: pos_id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newCustomerId) {
|
||||||
|
salesInvoiceData.customer = {
|
||||||
|
connect: {
|
||||||
|
id: newCustomerId || undefined,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const salesInvoice = await $tx.salesInvoice.create({
|
||||||
|
data: salesInvoiceData,
|
||||||
})
|
})
|
||||||
|
|
||||||
return salesInvoice
|
return salesInvoice
|
||||||
|
|||||||
Reference in New Issue
Block a user