mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 04:10:32 +00:00
Compare commits
37 Commits
v0.23.0-rc
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8def1d31d2 | ||
|
|
afab02a053 | ||
|
|
8e925c62f2 | ||
|
|
1b7d513adf | ||
|
|
7d764fb390 | ||
|
|
c571f50a74 | ||
|
|
6549026344 | ||
|
|
0963394b04 | ||
|
|
6cab0651fc | ||
|
|
4af537d6dd | ||
|
|
34db64612c | ||
|
|
10225bbfed | ||
|
|
c3a4fe6b37 | ||
|
|
02be959461 | ||
|
|
d5bf56e333 | ||
|
|
e3182c15b3 | ||
|
|
dfa63ece21 | ||
|
|
6e95bd7da1 | ||
|
|
f51fffa5c7 | ||
|
|
6193358cc3 | ||
|
|
518abcd30d | ||
|
|
7874b9f765 | ||
|
|
02cc7e0c96 | ||
|
|
57cc513873 | ||
|
|
f5bfdede30 | ||
|
|
488556bb59 | ||
|
|
0fc5a66e95 | ||
|
|
d9ae51027e | ||
|
|
a92d6112d9 | ||
|
|
889b0cec4b | ||
|
|
1c4c41ebba | ||
|
|
421f0c26a7 | ||
|
|
f461cc221b | ||
|
|
acae75a912 | ||
|
|
b5a69971a9 | ||
|
|
04d065b969 | ||
|
|
ca910ee489 |
93
.dockerignore
Normal file
93
.dockerignore
Normal file
@@ -0,0 +1,93 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
**/node_modules/
|
||||
.pnpm-store/
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
build/
|
||||
**/dist/
|
||||
**/build/
|
||||
*.tsbuildinfo
|
||||
|
||||
# Development files
|
||||
.git/
|
||||
.gitignore
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Test files
|
||||
test/
|
||||
**/test/
|
||||
**/*.spec.ts
|
||||
**/*.test.ts
|
||||
**/*.e2e-spec.ts
|
||||
coverage/
|
||||
.nyc_output/
|
||||
test-results/
|
||||
playwright-report/
|
||||
|
||||
# Documentation
|
||||
*.md
|
||||
!README.md
|
||||
docs/
|
||||
CHANGELOG.md
|
||||
CONTRIBUTING.md
|
||||
DISCLAIMER
|
||||
LICENSE
|
||||
|
||||
# CI/CD
|
||||
.github/
|
||||
.gitpod.yml
|
||||
|
||||
# Environment files
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
logs/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Docker files (don't copy Dockerfiles into themselves)
|
||||
docker-compose*.yml
|
||||
Dockerfile*
|
||||
.dockerignore
|
||||
|
||||
# Misc
|
||||
.cache/
|
||||
.temp/
|
||||
tmp/
|
||||
*.tmp
|
||||
.qodo/
|
||||
e2e/
|
||||
playwright.config.ts
|
||||
|
||||
# Source maps (not needed in production)
|
||||
*.map
|
||||
|
||||
# TypeScript configs (not needed at runtime)
|
||||
tsconfig*.json
|
||||
!tsconfig.json
|
||||
|
||||
# Linting/formatting
|
||||
.eslintrc*
|
||||
.prettierrc*
|
||||
.eslintcache
|
||||
|
||||
# Package manager locks (we copy them explicitly)
|
||||
# pnpm-lock.yaml
|
||||
@@ -58,6 +58,12 @@ services:
|
||||
# System database
|
||||
- SYSTEM_DB_NAME=${SYSTEM_DB_NAME}
|
||||
|
||||
# Redis
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- QUEUE_HOST=redis
|
||||
- QUEUE_PORT=6379
|
||||
|
||||
# Tenants databases
|
||||
- TENANT_DB_NAME_PERFIX=${TENANT_DB_NAME_PERFIX}
|
||||
|
||||
|
||||
@@ -35,4 +35,4 @@ WORKDIR /app/packages/server
|
||||
RUN git clone https://github.com/vishnubob/wait-for-it.git
|
||||
|
||||
# Once we listen the mysql port run the migration task.
|
||||
CMD ./wait-for-it/wait-for-it.sh mysql:3306 -- sh -c "node ./build/commands.js system:migrate:latest && node ./build/commands.js tenants:migrate:latest"
|
||||
CMD ./wait-for-it/wait-for-it.sh mysql:3306 -- sh -c "node dist/cli.js system:migrate:latest && node dist/cli.js tenants:migrate:latest"
|
||||
|
||||
@@ -1,100 +1,102 @@
|
||||
FROM node:18.16.0-alpine as build
|
||||
# Stage 1: Build
|
||||
FROM node:18.16.0-alpine AS builder
|
||||
|
||||
USER root
|
||||
|
||||
ARG MAIL_HOST= \
|
||||
MAIL_USERNAME= \
|
||||
MAIL_PASSWORD= \
|
||||
MAIL_PORT= \
|
||||
MAIL_SECURE= \
|
||||
MAIL_FROM_NAME= \
|
||||
MAIL_FROM_ADDRESS= \
|
||||
# Database
|
||||
DB_HOST= \
|
||||
DB_USER= \
|
||||
DB_PASSWORD= \
|
||||
DB_CHARSET= \
|
||||
# System database.
|
||||
SYSTEM_DB_NAME= \
|
||||
SYSTEM_DB_PASSWORD= \
|
||||
SYSTEM_DB_USER= \
|
||||
SYSTEM_DB_HOST= \
|
||||
SYSTEM_DB_CHARSET= \
|
||||
# Tenant databases.
|
||||
TENANT_DB_USER= \
|
||||
TENANT_DB_PASSWORD= \
|
||||
TENANT_DB_HOST= \
|
||||
TENANT_DB_NAME_PERFIX= \
|
||||
TENANT_DB_CHARSET= \
|
||||
# Authentication
|
||||
JWT_SECRET= \
|
||||
# Application
|
||||
BASE_URL= \
|
||||
# Sign-up restriction
|
||||
SIGNUP_DISABLED= \
|
||||
SIGNUP_ALLOWED_DOMAINS= \
|
||||
SIGNUP_ALLOWED_EMAILS=
|
||||
|
||||
ENV MAIL_HOST=$MAIL_HOST \
|
||||
MAIL_USERNAME=$MAIL_USERNAME \
|
||||
MAIL_PASSWORD=$MAIL_PASSWORD \
|
||||
MAIL_PORT=$MAIL_PORT \
|
||||
MAIL_SECURE=$MAIL_SECURE \
|
||||
MAIL_FROM_NAME=$MAIL_FROM_NAME \
|
||||
MAIL_FROM_ADDRESS=$MAIL_FROM_ADDRESS \
|
||||
# Database
|
||||
DB_HOST=$DB_HOST \
|
||||
DB_USER=$DB_USER \
|
||||
DB_PASSWORD=$DB_PASSWORD \
|
||||
DB_CHARSET=$DB_CHARSET \
|
||||
# System database.
|
||||
SYSTEM_DB_HOST=$SYSTEM_DB_HOST \
|
||||
SYSTEM_DB_USER=$SYSTEM_DB_USER \
|
||||
SYSTEM_DB_PASSWORD=$SYSTEM_DB_PASSWORD \
|
||||
SYSTEM_DB_NAME=$SYSTEM_DB_NAME \
|
||||
SYSTEM_DB_CHARSET=$SYSTEM_DB_CHARSET \
|
||||
# Tenant databases.
|
||||
TENANT_DB_NAME_PERFIX=$TENANT_DB_NAME_PERFIX \
|
||||
TENANT_DB_HOST=$TENANT_DB_HOST \
|
||||
TENANT_DB_PASSWORD=$TENANT_DB_PASSWORD \
|
||||
TENANT_DB_USER=$TENANT_DB_USER \
|
||||
TENANT_DB_CHARSET=$TENANT_DB_CHARSET \
|
||||
# Authentication
|
||||
JWT_SECRET=$JWT_SECRET \
|
||||
# Application
|
||||
BASE_URL=$BASE_URL \
|
||||
# Sign-up restriction
|
||||
SIGNUP_DISABLED=$SIGNUP_DISABLED \
|
||||
SIGNUP_ALLOWED_DOMAINS=$SIGNUP_ALLOWED_DOMAINS \
|
||||
SIGNUP_ALLOWED_EMAILS=$SIGNUP_ALLOWED_EMAILS
|
||||
|
||||
# New Relic config file.
|
||||
ENV NEW_RELIC_NO_CONFIG_FILE=true
|
||||
|
||||
# Create app directory.
|
||||
WORKDIR /app
|
||||
|
||||
RUN chown node:node /
|
||||
# Install pnpm
|
||||
RUN npm install -g pnpm@8.10.2
|
||||
|
||||
# Install pnpm
|
||||
RUN npm install -g pnpm
|
||||
# Install build dependencies
|
||||
RUN apk add --no-cache python3 build-base chromium
|
||||
|
||||
# Copy application dependency manifests to the container image.
|
||||
COPY --chown=node:node ./ ./
|
||||
|
||||
# Install application dependencies
|
||||
RUN apk update
|
||||
RUN apk add python3 build-base chromium
|
||||
|
||||
# Set PYHTON env
|
||||
# Set Python environment
|
||||
ENV PYTHON=/usr/bin/python3
|
||||
|
||||
# Install packages dependencies for production.
|
||||
RUN pnpm install
|
||||
# Copy package files for dependency installation
|
||||
COPY --chown=node:node package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./
|
||||
COPY --chown=node:node packages/server/package.json ./packages/server/
|
||||
COPY --chown=node:node shared/bigcapital-utils/package.json ./shared/bigcapital-utils/
|
||||
COPY --chown=node:node shared/pdf-templates/package.json ./shared/pdf-templates/
|
||||
COPY --chown=node:node shared/email-components/package.json ./shared/email-components/
|
||||
|
||||
# Install all dependencies (including devDependencies for build)
|
||||
RUN pnpm install --frozen-lockfile
|
||||
|
||||
# Copy source code
|
||||
COPY --chown=node:node ./packages/server ./packages/server
|
||||
COPY --chown=node:node ./shared/bigcapital-utils ./shared/bigcapital-utils
|
||||
COPY --chown=node:node ./shared/pdf-templates ./shared/pdf-templates
|
||||
COPY --chown=node:node ./shared/email-components ./shared/email-components
|
||||
|
||||
# # Creates a "dist" folder with the production build
|
||||
# Build NestJS application
|
||||
RUN pnpm run build:server --skip-nx-cache
|
||||
|
||||
CMD [ "node", "./packages/server/build/index.js" ]
|
||||
# Stage 2: Production
|
||||
FROM node:18.16.0-alpine AS production
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Install pnpm for production
|
||||
RUN npm install -g pnpm@8.10.2
|
||||
|
||||
# Create non-root user
|
||||
RUN addgroup -g 1001 -S nodejs && \
|
||||
adduser -S nodejs -u 1001
|
||||
|
||||
# Install build dependencies for native modules (bcrypt, etc.)
|
||||
RUN apk add --no-cache python3 build-base
|
||||
|
||||
# Set Python environment
|
||||
ENV PYTHON=/usr/bin/python3
|
||||
|
||||
# Copy package files for production dependency installation
|
||||
COPY --chown=nodejs:nodejs package.json pnpm-lock.yaml pnpm-workspace.yaml ./
|
||||
COPY --chown=nodejs:nodejs packages/server/package.json ./packages/server/
|
||||
COPY --chown=nodejs:nodejs shared/bigcapital-utils/package.json ./shared/bigcapital-utils/
|
||||
COPY --chown=nodejs:nodejs shared/pdf-templates/package.json ./shared/pdf-templates/
|
||||
COPY --chown=nodejs:nodejs shared/email-components/package.json ./shared/email-components/
|
||||
|
||||
# Copy .husky directory (needed for husky install command)
|
||||
COPY --chown=nodejs:nodejs .husky ./.husky
|
||||
|
||||
# Install only production dependencies
|
||||
# Install husky temporarily so prepare script can run, then remove it
|
||||
RUN pnpm add -D -w husky && \
|
||||
pnpm install --prod --frozen-lockfile && \
|
||||
pnpm remove -w husky && \
|
||||
# Remove build dependencies to reduce image size
|
||||
apk del python3 build-base
|
||||
|
||||
# Copy built application from builder stage
|
||||
COPY --from=builder --chown=nodejs:nodejs /app/packages/server/dist ./packages/server/dist
|
||||
|
||||
# Copy static assets (i18n, public, static directories)
|
||||
COPY --from=builder --chown=nodejs:nodejs /app/packages/server/src/i18n ./packages/server/dist/i18n
|
||||
COPY --from=builder --chown=nodejs:nodejs /app/packages/server/public ./packages/server/public
|
||||
COPY --from=builder --chown=nodejs:nodejs /app/packages/server/static ./packages/server/static
|
||||
|
||||
# Copy database migration files (needed for running migrations)
|
||||
COPY --from=builder --chown=nodejs:nodejs /app/packages/server/src/database ./packages/server/src/database
|
||||
|
||||
# Copy built shared packages (dist folders and package.json for module resolution)
|
||||
COPY --from=builder --chown=nodejs:nodejs /app/shared/bigcapital-utils/dist ./shared/bigcapital-utils/dist
|
||||
COPY --from=builder --chown=nodejs:nodejs /app/shared/pdf-templates/dist ./shared/pdf-templates/dist
|
||||
COPY --from=builder --chown=nodejs:nodejs /app/shared/email-components/dist ./shared/email-components/dist
|
||||
|
||||
# Set runtime environment variables (these should be provided at runtime via docker-compose or k8s)
|
||||
ENV NODE_ENV=production
|
||||
ENV NEW_RELIC_NO_CONFIG_FILE=true
|
||||
ENV PORT=3000
|
||||
|
||||
# Switch to non-root user
|
||||
USER nodejs
|
||||
|
||||
# Expose port
|
||||
EXPOSE 3000
|
||||
|
||||
# Health check - uses /api/system_db ping endpoint
|
||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
|
||||
CMD node -e "require('http').get('http://localhost:3000/api/system_db', (r) => {process.exit(r.statusCode >= 200 && r.statusCode < 300 ? 0 : 1)}).on('error', () => process.exit(1))"
|
||||
|
||||
# Start the application
|
||||
CMD [ "node", "packages/server/dist/main.js" ]
|
||||
|
||||
@@ -2,10 +2,23 @@
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"entryFile": "main",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true,
|
||||
"assets": [
|
||||
{ "include": "i18n/**/*", "watchAssets": true }
|
||||
{ "include": "i18n/**/*", "watchAssets": true },
|
||||
{ "include": "database/**/*", "exclude": "**/*.ts", "watchAssets": true }
|
||||
]
|
||||
},
|
||||
"projects": {
|
||||
"cli": {
|
||||
"type": "application",
|
||||
"root": "src",
|
||||
"entryFile": "cli",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"tsConfigPath": "tsconfig.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,9 @@
|
||||
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
|
||||
"@liaoliaots/nestjs-redis": "^10.0.0",
|
||||
"@nest-lab/throttler-storage-redis": "^1.1.0",
|
||||
"@bull-board/api": "^5.22.0",
|
||||
"@bull-board/express": "^5.22.0",
|
||||
"@bull-board/nestjs": "^5.22.0",
|
||||
"@nestjs/bull": "^10.2.1",
|
||||
"@nestjs/bullmq": "^10.2.2",
|
||||
"@nestjs/cache-manager": "^2.2.2",
|
||||
|
||||
8
packages/server/src/common/config/bull-board.ts
Normal file
8
packages/server/src/common/config/bull-board.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
import { parseBoolean } from '@/utils/parse-boolean';
|
||||
|
||||
export default registerAs('bullBoard', () => ({
|
||||
enabled: parseBoolean<boolean>(process.env.BULL_BOARD_ENABLED, false),
|
||||
username: process.env.BULL_BOARD_USERNAME,
|
||||
password: process.env.BULL_BOARD_PASSWORD,
|
||||
}));
|
||||
@@ -17,6 +17,9 @@ import loops from './loops';
|
||||
import bankfeed from './bankfeed';
|
||||
import throttle from './throttle';
|
||||
import cloud from './cloud';
|
||||
import redis from './redis';
|
||||
import queue from './queue';
|
||||
import bullBoard from './bull-board';
|
||||
|
||||
export const config = [
|
||||
app,
|
||||
@@ -38,4 +41,7 @@ export const config = [
|
||||
loops,
|
||||
bankfeed,
|
||||
throttle,
|
||||
redis,
|
||||
queue,
|
||||
bullBoard,
|
||||
];
|
||||
|
||||
6
packages/server/src/common/config/queue.ts
Normal file
6
packages/server/src/common/config/queue.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { registerAs } from '@nestjs/config';
|
||||
|
||||
export default registerAs('queue', () => ({
|
||||
host: process.env.QUEUE_HOST || 'localhost',
|
||||
port: parseInt(process.env.QUEUE_PORT, 10) || 6379,
|
||||
}));
|
||||
59
packages/server/src/middleware/bull-board-auth.middleware.ts
Normal file
59
packages/server/src/middleware/bull-board-auth.middleware.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
/**
|
||||
* Creates Express middleware for the Bull Board UI:
|
||||
* - When disabled: responds with 404.
|
||||
* - When enabled and username/password are set: enforces HTTP Basic Auth (401 if invalid).
|
||||
* - When enabled and credentials are not set: allows access (no auth).
|
||||
*/
|
||||
export function createBullBoardAuthMiddleware(
|
||||
enabled: boolean,
|
||||
username: string | undefined,
|
||||
password: string | undefined,
|
||||
): (req: Request, res: Response, next: NextFunction) => void {
|
||||
return (req: Request, res: Response, next: NextFunction) => {
|
||||
if (!enabled) {
|
||||
res.status(404).send('Not Found');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!username || !password) {
|
||||
return next();
|
||||
}
|
||||
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader || !authHeader.startsWith('Basic ')) {
|
||||
res.setHeader('WWW-Authenticate', 'Basic realm="Bull Board"');
|
||||
res.status(401).send('Authentication required');
|
||||
return;
|
||||
}
|
||||
|
||||
const base64Credentials = authHeader.slice(6);
|
||||
let decoded: string;
|
||||
try {
|
||||
decoded = Buffer.from(base64Credentials, 'base64').toString('utf8');
|
||||
} catch {
|
||||
res.setHeader('WWW-Authenticate', 'Basic realm="Bull Board"');
|
||||
res.status(401).send('Invalid credentials');
|
||||
return;
|
||||
}
|
||||
|
||||
const colonIndex = decoded.indexOf(':');
|
||||
if (colonIndex === -1) {
|
||||
res.setHeader('WWW-Authenticate', 'Basic realm="Bull Board"');
|
||||
res.status(401).send('Invalid credentials');
|
||||
return;
|
||||
}
|
||||
|
||||
const reqUsername = decoded.slice(0, colonIndex);
|
||||
const reqPassword = decoded.slice(colonIndex + 1);
|
||||
|
||||
if (reqUsername !== username || reqPassword !== password) {
|
||||
res.setHeader('WWW-Authenticate', 'Basic realm="Bull Board"');
|
||||
res.status(401).send('Invalid credentials');
|
||||
return;
|
||||
}
|
||||
|
||||
next();
|
||||
};
|
||||
}
|
||||
@@ -33,6 +33,7 @@ export class AccountTransaction extends BaseModel {
|
||||
public readonly userId!: number;
|
||||
public readonly itemId!: number;
|
||||
public readonly projectId!: number;
|
||||
public readonly costable!: boolean;
|
||||
public readonly account: Account;
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,9 @@ import {
|
||||
I18nModule,
|
||||
QueryResolver,
|
||||
} from 'nestjs-i18n';
|
||||
import { BullBoardModule } from '@bull-board/nestjs';
|
||||
import { ExpressAdapter } from '@bull-board/express';
|
||||
import { createBullBoardAuthMiddleware } from '@/middleware/bull-board-auth.middleware';
|
||||
import { BullModule } from '@nestjs/bullmq';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
import { PassportModule } from '@nestjs/passport';
|
||||
@@ -137,12 +140,30 @@ import { AppThrottleModule } from './AppThrottle.module';
|
||||
imports: [ConfigModule],
|
||||
useFactory: async (configService: ConfigService) => ({
|
||||
connection: {
|
||||
host: configService.get('QUEUE_HOST'),
|
||||
port: configService.get('QUEUE_PORT'),
|
||||
host: configService.get('queue.host'),
|
||||
port: configService.get('queue.port'),
|
||||
},
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
BullBoardModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: (configService: ConfigService) => {
|
||||
const enabled = configService.get<boolean>('bullBoard.enabled');
|
||||
const username = configService.get<string>('bullBoard.username');
|
||||
const password = configService.get<string>('bullBoard.password');
|
||||
return {
|
||||
route: '/queues',
|
||||
adapter: ExpressAdapter,
|
||||
middleware: createBullBoardAuthMiddleware(
|
||||
enabled,
|
||||
username,
|
||||
password,
|
||||
),
|
||||
};
|
||||
},
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
ClsModule.forRoot({
|
||||
global: true,
|
||||
middleware: {
|
||||
@@ -158,8 +179,8 @@ import { AppThrottleModule } from './AppThrottle.module';
|
||||
imports: [ConfigModule],
|
||||
useFactory: (configService: ConfigService) => ({
|
||||
config: {
|
||||
host: configService.get('redis.host') || 'localhost',
|
||||
port: configService.get('redis.port') || 6379,
|
||||
host: configService.get('redis.host'),
|
||||
port: configService.get('redis.port'),
|
||||
},
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
|
||||
@@ -17,6 +17,8 @@ import { PassportModule } from '@nestjs/passport';
|
||||
import { APP_GUARD } from '@nestjs/core';
|
||||
import { JwtAuthGuard } from './guards/jwt.guard';
|
||||
import { AuthMailSubscriber } from './subscribers/AuthMail.subscriber';
|
||||
import { BullBoardModule } from '@bull-board/nestjs';
|
||||
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
|
||||
import { BullModule } from '@nestjs/bullmq';
|
||||
import {
|
||||
SendResetPasswordMailQueue,
|
||||
@@ -63,6 +65,14 @@ const models = [
|
||||
TenancyModule,
|
||||
BullModule.registerQueue({ name: SendResetPasswordMailQueue }),
|
||||
BullModule.registerQueue({ name: SendSignupVerificationMailQueue }),
|
||||
BullBoardModule.forFeature({
|
||||
name: SendResetPasswordMailQueue,
|
||||
adapter: BullMQAdapter,
|
||||
}),
|
||||
BullBoardModule.forFeature({
|
||||
name: SendSignupVerificationMailQueue,
|
||||
adapter: BullMQAdapter,
|
||||
}),
|
||||
],
|
||||
exports: [...models],
|
||||
providers: [
|
||||
@@ -98,4 +108,4 @@ const models = [
|
||||
AuthMailSubscriber,
|
||||
],
|
||||
})
|
||||
export class AuthModule { }
|
||||
export class AuthModule {}
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import { Scope } from '@nestjs/common';
|
||||
import {
|
||||
SendResetPasswordMailJob,
|
||||
SendResetPasswordMailQueue,
|
||||
} from '../Auth.constants';
|
||||
import { Process } from '@nestjs/bull';
|
||||
import { SendResetPasswordMailQueue } from '../Auth.constants';
|
||||
import { Job } from 'bullmq';
|
||||
import { AuthenticationMailMesssages } from '../AuthMailMessages.esrvice';
|
||||
import { MailTransporter } from '@/modules/Mail/MailTransporter.service';
|
||||
@@ -23,7 +19,6 @@ export class SendResetPasswordMailProcessor extends WorkerHost {
|
||||
super();
|
||||
}
|
||||
|
||||
@Process(SendResetPasswordMailJob)
|
||||
async process(job: Job<SendResetPasswordMailJobPayload>) {
|
||||
try {
|
||||
await this.authMailMesssages.sendResetPasswordMail(
|
||||
|
||||
@@ -1,11 +1,7 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
import { Job } from 'bullmq';
|
||||
import { Process } from '@nestjs/bull';
|
||||
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import {
|
||||
SendSignupVerificationMailJob,
|
||||
SendSignupVerificationMailQueue,
|
||||
} from '../Auth.constants';
|
||||
import { SendSignupVerificationMailQueue } from '../Auth.constants';
|
||||
import { MailTransporter } from '@/modules/Mail/MailTransporter.service';
|
||||
import { AuthenticationMailMesssages } from '../AuthMailMessages.esrvice';
|
||||
|
||||
@@ -21,7 +17,6 @@ export class SendSignupVerificationMailProcessor extends WorkerHost {
|
||||
super();
|
||||
}
|
||||
|
||||
@Process(SendSignupVerificationMailJob)
|
||||
async process(job: Job<SendSignupVerificationMailJobPayload>) {
|
||||
try {
|
||||
await this.authMailMesssages.sendSignupVerificationMail(
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { BullBoardModule } from '@bull-board/nestjs';
|
||||
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
|
||||
import { BullModule } from '@nestjs/bullmq';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { SocketModule } from '../Socket/Socket.module';
|
||||
@@ -33,6 +35,10 @@ const models = [RegisterTenancyModel(PlaidItem)];
|
||||
BankingCategorizeModule,
|
||||
BankingTransactionsModule,
|
||||
BullModule.registerQueue({ name: UpdateBankingPlaidTransitionsQueueJob }),
|
||||
BullBoardModule.forFeature({
|
||||
name: UpdateBankingPlaidTransitionsQueueJob,
|
||||
adapter: BullMQAdapter,
|
||||
}),
|
||||
...models,
|
||||
],
|
||||
providers: [
|
||||
@@ -51,4 +57,4 @@ const models = [RegisterTenancyModel(PlaidItem)];
|
||||
exports: [...models],
|
||||
controllers: [BankingPlaidController, BankingPlaidWebhooksController],
|
||||
})
|
||||
export class BankingPlaidModule { }
|
||||
export class BankingPlaidModule {}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { Process } from '@nestjs/bull';
|
||||
import { UseCls } from 'nestjs-cls';
|
||||
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import { Scope } from '@nestjs/common';
|
||||
import { Job } from 'bullmq';
|
||||
import {
|
||||
PlaidFetchTransitonsEventPayload,
|
||||
UpdateBankingPlaidTransitionsJob,
|
||||
UpdateBankingPlaidTransitionsQueueJob,
|
||||
} from '../types/BankingPlaid.types';
|
||||
import { PlaidUpdateTransactions } from '../command/PlaidUpdateTransactions';
|
||||
@@ -28,7 +26,6 @@ export class PlaidFetchTransactionsProcessor extends WorkerHost {
|
||||
/**
|
||||
* Triggers the function.
|
||||
*/
|
||||
@Process(UpdateBankingPlaidTransitionsJob)
|
||||
@UseCls()
|
||||
async process(job: Job<PlaidFetchTransitonsEventPayload>) {
|
||||
const { plaidItemId } = job.data;
|
||||
|
||||
@@ -10,6 +10,8 @@ import { BankingRecognizedTransactionsController } from './BankingRecognizedTran
|
||||
import { RecognizedTransactionsApplication } from './RecognizedTransactions.application';
|
||||
import { GetRecognizedTransactionsService } from './GetRecongizedTransactions';
|
||||
import { GetRecognizedTransactionService } from './queries/GetRecognizedTransaction.service';
|
||||
import { BullBoardModule } from '@bull-board/nestjs';
|
||||
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
|
||||
import { BullModule } from '@nestjs/bullmq';
|
||||
import { RecognizeUncategorizedTransactionsQueue } from './_types';
|
||||
import { RegonizeTransactionsPrcessor } from './jobs/RecognizeTransactionsJob';
|
||||
@@ -25,6 +27,10 @@ const models = [RegisterTenancyModel(RecognizedBankTransaction)];
|
||||
BullModule.registerQueue({
|
||||
name: RecognizeUncategorizedTransactionsQueue,
|
||||
}),
|
||||
BullBoardModule.forFeature({
|
||||
name: RecognizeUncategorizedTransactionsQueue,
|
||||
adapter: BullMQAdapter,
|
||||
}),
|
||||
...models,
|
||||
],
|
||||
providers: [
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
RecognizeUncategorizedTransactionsJobPayload,
|
||||
RecognizeUncategorizedTransactionsQueue,
|
||||
} from '../_types';
|
||||
import { Process } from '@nestjs/bull';
|
||||
|
||||
@Processor({
|
||||
name: RecognizeUncategorizedTransactionsQueue,
|
||||
@@ -28,7 +27,6 @@ export class RegonizeTransactionsPrcessor extends WorkerHost {
|
||||
/**
|
||||
* Triggers sending invoice mail.
|
||||
*/
|
||||
@Process(RecognizeUncategorizedTransactionsQueue)
|
||||
@UseCls()
|
||||
async process(job: Job<RecognizeUncategorizedTransactionsJobPayload>) {
|
||||
const { ruleId, transactionsCriteria } = job.data;
|
||||
|
||||
@@ -2,9 +2,11 @@ import { forwardRef, Module } from '@nestjs/common';
|
||||
import { TransactionLandedCostEntriesService } from './TransactionLandedCostEntries.service';
|
||||
import { AllocateLandedCostService } from './commands/AllocateLandedCost.service';
|
||||
import { LandedCostGLEntriesSubscriber } from './commands/LandedCostGLEntries.subscriber';
|
||||
// import { LandedCostGLEntries } from './commands/LandedCostGLEntries.service';
|
||||
import { LandedCostGLEntriesService } from './commands/LandedCostGLEntries.service';
|
||||
import { LandedCostSyncCostTransactions } from './commands/LandedCostSyncCostTransactions.service';
|
||||
import { LedgerModule } from '../Ledger/Ledger.module';
|
||||
import { LandedCostSyncCostTransactionsSubscriber } from './commands/LandedCostSyncCostTransactions.subscriber';
|
||||
import { LandedCostInventoryTransactionsSubscriber } from './commands/LandedCostInventoryTransactions.subscriber';
|
||||
import { BillAllocatedLandedCostTransactions } from './commands/BillAllocatedLandedCostTransactions.service';
|
||||
import { BillAllocateLandedCostController } from './LandedCost.controller';
|
||||
import { RevertAllocatedLandedCost } from './commands/RevertAllocatedLandedCost.service';
|
||||
@@ -16,12 +18,12 @@ import { ExpenseLandedCost } from './commands/ExpenseLandedCost.service';
|
||||
import { BillLandedCost } from './commands/BillLandedCost.service';
|
||||
|
||||
@Module({
|
||||
imports: [forwardRef(() => InventoryCostModule)],
|
||||
imports: [forwardRef(() => InventoryCostModule), LedgerModule],
|
||||
providers: [
|
||||
AllocateLandedCostService,
|
||||
TransactionLandedCostEntriesService,
|
||||
BillAllocatedLandedCostTransactions,
|
||||
LandedCostGLEntriesSubscriber,
|
||||
LandedCostGLEntriesService,
|
||||
TransactionLandedCost,
|
||||
BillLandedCost,
|
||||
ExpenseLandedCost,
|
||||
@@ -29,6 +31,8 @@ import { BillLandedCost } from './commands/BillLandedCost.service';
|
||||
RevertAllocatedLandedCost,
|
||||
LandedCostInventoryTransactions,
|
||||
LandedCostTranasctions,
|
||||
LandedCostGLEntriesSubscriber,
|
||||
LandedCostInventoryTransactionsSubscriber,
|
||||
LandedCostSyncCostTransactionsSubscriber,
|
||||
],
|
||||
exports: [TransactionLandedCostEntriesService],
|
||||
|
||||
@@ -23,7 +23,9 @@ export class AllocateLandedCostService extends BaseLandedCostService {
|
||||
private readonly billModel: TenantModelProxy<typeof Bill>,
|
||||
|
||||
@Inject(BillLandedCost.name)
|
||||
protected readonly billLandedCostModel: TenantModelProxy<typeof BillLandedCost>
|
||||
protected readonly billLandedCostModel: TenantModelProxy<
|
||||
typeof BillLandedCost
|
||||
>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -54,7 +56,8 @@ export class AllocateLandedCostService extends BaseLandedCostService {
|
||||
const amount = this.getAllocateItemsCostTotal(allocateCostDTO);
|
||||
|
||||
// Retrieve the purchase invoice or throw not found error.
|
||||
const bill = await this.billModel().query()
|
||||
const bill = await this.billModel()
|
||||
.query()
|
||||
.findById(billId)
|
||||
.withGraphFetched('entries')
|
||||
.throwIfNotFound();
|
||||
@@ -89,8 +92,9 @@ export class AllocateLandedCostService extends BaseLandedCostService {
|
||||
// unit-of-work eniverment.
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Save the bill landed cost model.
|
||||
const billLandedCost =
|
||||
await BillLandedCost.query(trx).insertGraph(billLandedCostObj);
|
||||
const billLandedCost = await this.billLandedCostModel()
|
||||
.query(trx)
|
||||
.insertGraph(billLandedCostObj);
|
||||
// Triggers `onBillLandedCostCreated` event.
|
||||
await this.eventPublisher.emitAsync(events.billLandedCost.onCreated, {
|
||||
bill,
|
||||
@@ -103,5 +107,5 @@ export class AllocateLandedCostService extends BaseLandedCostService {
|
||||
|
||||
return billLandedCost;
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,236 +1,188 @@
|
||||
// import * as R from 'ramda';
|
||||
// import { Knex } from 'knex';
|
||||
// import { Inject, Injectable } from '@nestjs/common';
|
||||
// import { BaseLandedCostService } from '../BaseLandedCost.service';
|
||||
// import { BillLandedCost } from '../models/BillLandedCost';
|
||||
// import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
// import { Bill } from '@/modules/Bills/models/Bill';
|
||||
// import { BillLandedCostEntry } from '../models/BillLandedCostEntry';
|
||||
// import { ILedger, ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
|
||||
// import { Ledger } from '@/modules/Ledger/Ledger';
|
||||
// import { AccountNormal } from '@/interfaces/Account';
|
||||
// import { ILandedCostTransactionEntry } from '../types/BillLandedCosts.types';
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as moment from 'moment';
|
||||
import { BaseLandedCostService } from '../BaseLandedCost.service';
|
||||
import { BillLandedCost } from '../models/BillLandedCost';
|
||||
import { Bill } from '@/modules/Bills/models/Bill';
|
||||
import { BillLandedCostEntry } from '../models/BillLandedCostEntry';
|
||||
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
|
||||
import { Ledger } from '@/modules/Ledger/Ledger';
|
||||
import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service';
|
||||
import { AccountNormal } from '@/modules/Accounts/Accounts.types';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
// @Injectable()
|
||||
// export class LandedCostGLEntries extends BaseLandedCostService {
|
||||
// constructor(
|
||||
// private readonly journalService: JournalPosterService,
|
||||
// private readonly ledgerRepository: LedgerRepository,
|
||||
@Injectable()
|
||||
export class LandedCostGLEntriesService extends BaseLandedCostService {
|
||||
constructor(
|
||||
private readonly ledgerStorage: LedgerStorageService,
|
||||
|
||||
// @Inject(BillLandedCost.name)
|
||||
// private readonly billLandedCostModel: TenantModelProxy<typeof BillLandedCost>,
|
||||
// ) {
|
||||
// super();
|
||||
// }
|
||||
@Inject(BillLandedCost.name)
|
||||
protected readonly billLandedCostModel: TenantModelProxy<
|
||||
typeof BillLandedCost
|
||||
>,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Retrieves the landed cost GL common entry.
|
||||
// * @param {IBill} bill
|
||||
// * @param {IBillLandedCost} allocatedLandedCost
|
||||
// * @returns
|
||||
// */
|
||||
// private getLandedCostGLCommonEntry = (
|
||||
// bill: Bill,
|
||||
// allocatedLandedCost: BillLandedCost
|
||||
// ) => {
|
||||
// return {
|
||||
// date: bill.billDate,
|
||||
// currencyCode: allocatedLandedCost.currencyCode,
|
||||
// exchangeRate: allocatedLandedCost.exchangeRate,
|
||||
/**
|
||||
* Retrieves the landed cost GL common entry.
|
||||
*/
|
||||
private getLandedCostGLCommonEntry(
|
||||
bill: Bill,
|
||||
allocatedLandedCost: BillLandedCost,
|
||||
) {
|
||||
return {
|
||||
date: moment(bill.billDate).format('YYYY-MM-DD'),
|
||||
currencyCode: allocatedLandedCost.currencyCode,
|
||||
exchangeRate: allocatedLandedCost.exchangeRate,
|
||||
|
||||
// transactionType: 'LandedCost',
|
||||
// transactionId: allocatedLandedCost.id,
|
||||
// transactionNumber: bill.billNumber,
|
||||
transactionType: 'LandedCost',
|
||||
transactionId: allocatedLandedCost.id,
|
||||
transactionNumber: bill.billNumber,
|
||||
|
||||
// referenceNumber: bill.referenceNo,
|
||||
referenceNumber: bill.referenceNo,
|
||||
|
||||
// credit: 0,
|
||||
// debit: 0,
|
||||
// };
|
||||
// };
|
||||
branchId: bill.branchId,
|
||||
projectId: bill.projectId,
|
||||
|
||||
// /**
|
||||
// * Retrieves the landed cost GL inventory entry.
|
||||
// * @param {IBill} bill
|
||||
// * @param {IBillLandedCost} allocatedLandedCost
|
||||
// * @param {IBillLandedCostEntry} allocatedEntry
|
||||
// * @returns {ILedgerEntry}
|
||||
// */
|
||||
// private getLandedCostGLInventoryEntry = (
|
||||
// bill: Bill,
|
||||
// allocatedLandedCost: BillLandedCost,
|
||||
// allocatedEntry: BillLandedCostEntry
|
||||
// ): ILedgerEntry => {
|
||||
// const commonEntry = this.getLandedCostGLCommonEntry(
|
||||
// bill,
|
||||
// allocatedLandedCost
|
||||
// );
|
||||
// return {
|
||||
// ...commonEntry,
|
||||
// debit: allocatedLandedCost.localAmount,
|
||||
// accountId: allocatedEntry.itemEntry.item.inventoryAccountId,
|
||||
// index: 1,
|
||||
// accountNormal: AccountNormal.DEBIT,
|
||||
// };
|
||||
// };
|
||||
credit: 0,
|
||||
debit: 0,
|
||||
};
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Retrieves the landed cost GL cost entry.
|
||||
// * @param {IBill} bill
|
||||
// * @param {IBillLandedCost} allocatedLandedCost
|
||||
// * @param {ILandedCostTransactionEntry} fromTransactionEntry
|
||||
// * @returns {ILedgerEntry}
|
||||
// */
|
||||
// private getLandedCostGLCostEntry = (
|
||||
// bill: Bill,
|
||||
// allocatedLandedCost: BillLandedCost,
|
||||
// fromTransactionEntry: ILandedCostTransactionEntry
|
||||
// ): ILedgerEntry => {
|
||||
// const commonEntry = this.getLandedCostGLCommonEntry(
|
||||
// bill,
|
||||
// allocatedLandedCost
|
||||
// );
|
||||
// return {
|
||||
// ...commonEntry,
|
||||
// credit: allocatedLandedCost.localAmount,
|
||||
// accountId: fromTransactionEntry.costAccountId,
|
||||
// index: 2,
|
||||
// accountNormal: AccountNormal.CREDIT,
|
||||
// };
|
||||
// };
|
||||
/**
|
||||
* Retrieves the landed cost GL inventory entry for an allocated item.
|
||||
*/
|
||||
private getLandedCostGLInventoryEntry(
|
||||
bill: Bill,
|
||||
allocatedLandedCost: BillLandedCost,
|
||||
allocatedEntry: BillLandedCostEntry,
|
||||
index: number,
|
||||
): ILedgerEntry {
|
||||
const commonEntry = this.getLandedCostGLCommonEntry(
|
||||
bill,
|
||||
allocatedLandedCost,
|
||||
);
|
||||
const itemEntry = (
|
||||
allocatedEntry as BillLandedCostEntry & {
|
||||
itemEntry?: {
|
||||
item?: { type?: string; inventoryAccountId?: number };
|
||||
costAccountId?: number;
|
||||
itemId?: number;
|
||||
};
|
||||
}
|
||||
).itemEntry;
|
||||
const item = itemEntry?.item;
|
||||
const isInventory = item && ['inventory'].indexOf(item.type) !== -1;
|
||||
const accountId = isInventory
|
||||
? item?.inventoryAccountId
|
||||
: itemEntry?.costAccountId;
|
||||
|
||||
// /**
|
||||
// * Retrieve allocated landed cost entry GL entries.
|
||||
// * @param {IBill} bill
|
||||
// * @param {IBillLandedCost} allocatedLandedCost
|
||||
// * @param {ILandedCostTransactionEntry} fromTransactionEntry
|
||||
// * @param {IBillLandedCostEntry} allocatedEntry
|
||||
// * @returns {ILedgerEntry}
|
||||
// */
|
||||
// private getLandedCostGLAllocateEntry = R.curry(
|
||||
// (
|
||||
// bill: Bill,
|
||||
// allocatedLandedCost: BillLandedCost,
|
||||
// fromTransactionEntry: ILandedCostTransactionEntry,
|
||||
// allocatedEntry: BillLandedCostEntry
|
||||
// ): ILedgerEntry[] => {
|
||||
// const inventoryEntry = this.getLandedCostGLInventoryEntry(
|
||||
// bill,
|
||||
// allocatedLandedCost,
|
||||
// allocatedEntry
|
||||
// );
|
||||
// const costEntry = this.getLandedCostGLCostEntry(
|
||||
// bill,
|
||||
// allocatedLandedCost,
|
||||
// fromTransactionEntry
|
||||
// );
|
||||
// return [inventoryEntry, costEntry];
|
||||
// }
|
||||
// );
|
||||
if (!accountId) {
|
||||
throw new Error(
|
||||
`Cannot determine GL account for landed cost allocate entry (entryId: ${allocatedEntry.entryId})`,
|
||||
);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Compose the landed cost GL entries.
|
||||
// * @param {BillLandedCost} allocatedLandedCost
|
||||
// * @param {Bill} bill
|
||||
// * @param {ILandedCostTransactionEntry} fromTransactionEntry
|
||||
// * @returns {ILedgerEntry[]}
|
||||
// */
|
||||
// public getLandedCostGLEntries = (
|
||||
// allocatedLandedCost: BillLandedCost,
|
||||
// bill: Bill,
|
||||
// fromTransactionEntry: ILandedCostTransactionEntry
|
||||
// ): ILedgerEntry[] => {
|
||||
// const getEntry = this.getLandedCostGLAllocateEntry(
|
||||
// bill,
|
||||
// allocatedLandedCost,
|
||||
// fromTransactionEntry
|
||||
// );
|
||||
// return allocatedLandedCost.allocateEntries.map(getEntry).flat();
|
||||
// };
|
||||
const localAmount =
|
||||
allocatedEntry.cost * (allocatedLandedCost.exchangeRate || 1);
|
||||
|
||||
// /**
|
||||
// * Retrieves the landed cost GL ledger.
|
||||
// * @param {BillLandedCost} allocatedLandedCost
|
||||
// * @param {Bill} bill
|
||||
// * @param {ILandedCostTransactionEntry} fromTransactionEntry
|
||||
// * @returns {ILedger}
|
||||
// */
|
||||
// public getLandedCostLedger = (
|
||||
// allocatedLandedCost: BillLandedCost,
|
||||
// bill: Bill,
|
||||
// fromTransactionEntry: ILandedCostTransactionEntry
|
||||
// ): ILedger => {
|
||||
// const entries = this.getLandedCostGLEntries(
|
||||
// allocatedLandedCost,
|
||||
// bill,
|
||||
// fromTransactionEntry
|
||||
// );
|
||||
// return new Ledger(entries);
|
||||
// };
|
||||
return {
|
||||
...commonEntry,
|
||||
debit: localAmount,
|
||||
accountId,
|
||||
index: index + 1,
|
||||
indexGroup: 10,
|
||||
itemId: itemEntry?.itemId,
|
||||
accountNormal: AccountNormal.DEBIT,
|
||||
};
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Writes landed cost GL entries to the storage layer.
|
||||
// * @param {number} tenantId -
|
||||
// */
|
||||
// public writeLandedCostGLEntries = async (
|
||||
// allocatedLandedCost: BillLandedCost,
|
||||
// bill: Bill,
|
||||
// fromTransactionEntry: ILandedCostTransactionEntry,
|
||||
// trx?: Knex.Transaction
|
||||
// ) => {
|
||||
// const ledgerEntries = this.getLandedCostGLEntries(
|
||||
// allocatedLandedCost,
|
||||
// bill,
|
||||
// fromTransactionEntry
|
||||
// );
|
||||
// await this.ledgerRepository.saveLedgerEntries(ledgerEntries, trx);
|
||||
// };
|
||||
/**
|
||||
* Retrieves the landed cost GL cost entry (credit to cost account).
|
||||
*/
|
||||
private getLandedCostGLCostEntry(
|
||||
bill: Bill,
|
||||
allocatedLandedCost: BillLandedCost,
|
||||
): ILedgerEntry {
|
||||
const commonEntry = this.getLandedCostGLCommonEntry(
|
||||
bill,
|
||||
allocatedLandedCost,
|
||||
);
|
||||
|
||||
// /**
|
||||
// * Generates and writes GL entries of the given landed cost.
|
||||
// * @param {number} billLandedCostId
|
||||
// * @param {Knex.Transaction} trx
|
||||
// */
|
||||
// public createLandedCostGLEntries = async (
|
||||
// billLandedCostId: number,
|
||||
// trx?: Knex.Transaction
|
||||
// ) => {
|
||||
// // Retrieve the bill landed cost transacion with associated
|
||||
// // allocated entries and items.
|
||||
// const allocatedLandedCost = await this.billLandedCostModel().query(trx)
|
||||
// .findById(billLandedCostId)
|
||||
// .withGraphFetched('bill')
|
||||
// .withGraphFetched('allocateEntries.itemEntry.item');
|
||||
return {
|
||||
...commonEntry,
|
||||
credit: allocatedLandedCost.localAmount,
|
||||
accountId: allocatedLandedCost.costAccountId,
|
||||
index: 1,
|
||||
indexGroup: 20,
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
};
|
||||
}
|
||||
|
||||
// // Retrieve the allocated from transactione entry.
|
||||
// const transactionEntry = await this.getLandedCostEntry(
|
||||
// allocatedLandedCost.fromTransactionType,
|
||||
// allocatedLandedCost.fromTransactionId,
|
||||
// allocatedLandedCost.fromTransactionEntryId
|
||||
// );
|
||||
// // Writes the given landed cost GL entries to the storage layer.
|
||||
// await this.writeLandedCostGLEntries(
|
||||
// allocatedLandedCost,
|
||||
// allocatedLandedCost.bill,
|
||||
// transactionEntry,
|
||||
// trx
|
||||
// );
|
||||
// };
|
||||
/**
|
||||
* Composes the landed cost GL entries.
|
||||
*/
|
||||
public getLandedCostGLEntries(
|
||||
allocatedLandedCost: BillLandedCost,
|
||||
bill: Bill,
|
||||
): ILedgerEntry[] {
|
||||
const inventoryEntries = allocatedLandedCost.allocateEntries.map(
|
||||
(allocatedEntry, index) =>
|
||||
this.getLandedCostGLInventoryEntry(
|
||||
bill,
|
||||
allocatedLandedCost,
|
||||
allocatedEntry,
|
||||
index,
|
||||
),
|
||||
);
|
||||
const costEntry = this.getLandedCostGLCostEntry(bill, allocatedLandedCost);
|
||||
|
||||
// /**
|
||||
// * Reverts GL entries of the given allocated landed cost transaction.
|
||||
// * @param {number} tenantId
|
||||
// * @param {number} landedCostId
|
||||
// * @param {Knex.Transaction} trx
|
||||
// */
|
||||
// public revertLandedCostGLEntries = async (
|
||||
// landedCostId: number,
|
||||
// trx: Knex.Transaction
|
||||
// ) => {
|
||||
// await this.journalService.revertJournalTransactions(
|
||||
// landedCostId,
|
||||
// 'LandedCost',
|
||||
// trx
|
||||
// );
|
||||
// };
|
||||
// }
|
||||
return [...inventoryEntries, costEntry];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the landed cost GL ledger.
|
||||
*/
|
||||
public getLandedCostLedger(
|
||||
allocatedLandedCost: BillLandedCost,
|
||||
bill: Bill,
|
||||
): Ledger {
|
||||
const entries = this.getLandedCostGLEntries(allocatedLandedCost, bill);
|
||||
return new Ledger(entries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates and writes GL entries of the given landed cost.
|
||||
*/
|
||||
public createLandedCostGLEntries = async (
|
||||
billLandedCostId: number,
|
||||
trx?: Knex.Transaction,
|
||||
) => {
|
||||
const allocatedLandedCost = await this.billLandedCostModel()
|
||||
.query(trx)
|
||||
.findById(billLandedCostId)
|
||||
.withGraphFetched('bill')
|
||||
.withGraphFetched('allocateEntries.itemEntry.item');
|
||||
|
||||
if (!allocatedLandedCost?.bill) {
|
||||
throw new Error('BillLandedCost or associated Bill not found');
|
||||
}
|
||||
|
||||
const ledger = this.getLandedCostLedger(
|
||||
allocatedLandedCost,
|
||||
allocatedLandedCost.bill,
|
||||
);
|
||||
await this.ledgerStorage.commit(ledger, trx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reverts GL entries of the given allocated landed cost transaction.
|
||||
*/
|
||||
public revertLandedCostGLEntries = async (
|
||||
landedCostId: number,
|
||||
trx?: Knex.Transaction,
|
||||
) => {
|
||||
await this.ledgerStorage.deleteByReference(landedCostId, 'LandedCost', trx);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,14 +3,15 @@ import {
|
||||
IAllocatedLandedCostDeletedPayload,
|
||||
} from '../types/BillLandedCosts.types';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
// import { LandedCostGLEntries } from './LandedCostGLEntries.service';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { events } from '@/common/events/events';
|
||||
import { LandedCostGLEntriesService } from './LandedCostGLEntries.service';
|
||||
|
||||
@Injectable()
|
||||
export class LandedCostGLEntriesSubscriber {
|
||||
constructor() // private readonly billLandedCostGLEntries: LandedCostGLEntries,
|
||||
{}
|
||||
constructor(
|
||||
private readonly landedCostGLEntries: LandedCostGLEntriesService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Writes GL entries once landed cost transaction created.
|
||||
@@ -21,10 +22,10 @@ export class LandedCostGLEntriesSubscriber {
|
||||
billLandedCost,
|
||||
trx,
|
||||
}: IAllocatedLandedCostCreatedPayload) {
|
||||
// await this.billLandedCostGLEntries.createLandedCostGLEntries(
|
||||
// billLandedCost.id,
|
||||
// trx
|
||||
// );
|
||||
await this.landedCostGLEntries.createLandedCostGLEntries(
|
||||
billLandedCost.id,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,13 +33,13 @@ export class LandedCostGLEntriesSubscriber {
|
||||
* @param {IAllocatedLandedCostDeletedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.billLandedCost.onDeleted)
|
||||
async revertGLEnteriesOnceLandedCostDeleted({
|
||||
async revertGLEntriesOnceLandedCostDeleted({
|
||||
oldBillLandedCost,
|
||||
trx,
|
||||
}: IAllocatedLandedCostDeletedPayload) {
|
||||
// await this.billLandedCostGLEntries.revertLandedCostGLEntries(
|
||||
// oldBillLandedCost.id,
|
||||
// trx
|
||||
// );
|
||||
await this.landedCostGLEntries.revertLandedCostGLEntries(
|
||||
oldBillLandedCost.id,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export class BillBranchValidateSubscriber {
|
||||
* Validate branch existance on bill creating.
|
||||
* @param {IBillCreatingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.bill.onCreating)
|
||||
@OnEvent(events.bill.onCreating, { suppressErrors: false })
|
||||
async validateBranchExistanceOnBillCreating({
|
||||
billDTO,
|
||||
}: IBillCreatingPayload) {
|
||||
@@ -30,7 +30,7 @@ export class BillBranchValidateSubscriber {
|
||||
* Validate branch existance once bill editing.
|
||||
* @param {IBillEditingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.bill.onEditing)
|
||||
@OnEvent(events.bill.onEditing, { suppressErrors: false })
|
||||
async validateBranchExistanceOnBillEditing({ billDTO }: IBillEditingPayload) {
|
||||
await this.validateBranchExistance.validateTransactionBranchWhenActive(
|
||||
billDTO.branchId,
|
||||
|
||||
@@ -14,7 +14,7 @@ export class CashflowBranchDTOValidatorSubscriber {
|
||||
* Validate branch existance once cashflow transaction creating.
|
||||
* @param {ICommandCashflowCreatingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.cashflow.onTransactionCreating)
|
||||
@OnEvent(events.cashflow.onTransactionCreating, { suppressErrors: false })
|
||||
async validateBranchExistanceOnCashflowTransactionCreating({
|
||||
newTransactionDTO,
|
||||
}: ICommandCashflowCreatingPayload) {
|
||||
|
||||
@@ -15,13 +15,13 @@ import {
|
||||
export class ContactBranchValidateSubscriber {
|
||||
constructor(
|
||||
private readonly validateBranchExistance: ValidateBranchExistance,
|
||||
) { }
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Validate branch existance on customer creating.
|
||||
* @param {ICustomerEventCreatingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.customers.onCreating)
|
||||
@OnEvent(events.customers.onCreating, { suppressErrors: false })
|
||||
async validateBranchExistanceOnCustomerCreating({
|
||||
customerDTO,
|
||||
}: ICustomerEventCreatingPayload) {
|
||||
@@ -37,7 +37,7 @@ export class ContactBranchValidateSubscriber {
|
||||
* Validate branch existance once customer opening balance editing.
|
||||
* @param {ICustomerOpeningBalanceEditingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.customers.onOpeningBalanceChanging)
|
||||
@OnEvent(events.customers.onOpeningBalanceChanging, { suppressErrors: false })
|
||||
async validateBranchExistanceOnCustomerOpeningBalanceEditing({
|
||||
openingBalanceEditDTO,
|
||||
}: ICustomerOpeningBalanceEditingPayload) {
|
||||
@@ -52,7 +52,7 @@ export class ContactBranchValidateSubscriber {
|
||||
* Validates the branch existance on vendor creating.
|
||||
* @param {IVendorEventCreatingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.vendors.onCreating)
|
||||
@OnEvent(events.vendors.onCreating, { suppressErrors: false })
|
||||
async validateBranchExistanceonVendorCreating({
|
||||
vendorDTO,
|
||||
}: IVendorEventCreatingPayload) {
|
||||
@@ -68,7 +68,7 @@ export class ContactBranchValidateSubscriber {
|
||||
* Validate branch existance once the vendor opening balance editing.
|
||||
* @param {IVendorOpeningBalanceEditingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.vendors.onOpeningBalanceChanging)
|
||||
@OnEvent(events.vendors.onOpeningBalanceChanging, { suppressErrors: false })
|
||||
async validateBranchExistanceOnVendorOpeningBalanceEditing({
|
||||
openingBalanceEditDTO,
|
||||
}: IVendorOpeningBalanceEditingPayload) {
|
||||
|
||||
@@ -15,7 +15,7 @@ export class CreditNoteBranchValidateSubscriber {
|
||||
* Validate branch existance on credit note creating.
|
||||
* @param {ICreditNoteCreatingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.creditNote.onCreating)
|
||||
@OnEvent(events.creditNote.onCreating, { suppressErrors: false })
|
||||
async validateBranchExistanceOnCreditCreating({
|
||||
creditNoteDTO,
|
||||
}: ICreditNoteCreatingPayload) {
|
||||
@@ -28,7 +28,7 @@ export class CreditNoteBranchValidateSubscriber {
|
||||
* Validate branch existance once credit note editing.
|
||||
* @param {ICreditNoteEditingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.creditNote.onEditing)
|
||||
@OnEvent(events.creditNote.onEditing, { suppressErrors: false })
|
||||
async validateBranchExistanceOnCreditEditing({
|
||||
creditNoteEditDTO,
|
||||
}: ICreditNoteEditingPayload) {
|
||||
|
||||
@@ -14,7 +14,7 @@ export class CreditNoteRefundBranchValidateSubscriber {
|
||||
* Validate branch existance on refund credit note creating.
|
||||
* @param {IRefundCreditNoteCreatingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.creditNote.onRefundCreating)
|
||||
@OnEvent(events.creditNote.onRefundCreating, { suppressErrors: false })
|
||||
async validateBranchExistanceOnCreditRefundCreating({
|
||||
newCreditNoteDTO,
|
||||
}: IRefundCreditNoteCreatingPayload) {
|
||||
|
||||
@@ -16,7 +16,7 @@ export class ExpenseBranchValidateSubscriber {
|
||||
* Validate branch existance once expense transaction creating.
|
||||
* @param {IExpenseCreatingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.expenses.onCreating)
|
||||
@OnEvent(events.expenses.onCreating, { suppressErrors: false })
|
||||
async validateBranchExistanceOnExpenseCreating({
|
||||
expenseDTO,
|
||||
}: IExpenseCreatingPayload) {
|
||||
@@ -29,7 +29,7 @@ export class ExpenseBranchValidateSubscriber {
|
||||
* Validate branch existance once expense transaction editing.
|
||||
* @param {IExpenseEventEditingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.expenses.onEditing)
|
||||
@OnEvent(events.expenses.onEditing, { suppressErrors: false })
|
||||
async validateBranchExistanceOnExpenseEditing({
|
||||
expenseDTO,
|
||||
}: IExpenseEventEditingPayload) {
|
||||
|
||||
@@ -14,7 +14,7 @@ export class InventoryAdjustmentBranchValidateSubscriber {
|
||||
* Validate branch existance on inventory adjustment creating.
|
||||
* @param {IInventoryAdjustmentCreatingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.inventoryAdjustment.onQuickCreating)
|
||||
@OnEvent(events.inventoryAdjustment.onQuickCreating, { suppressErrors: false })
|
||||
async validateBranchExistanceOnInventoryCreating({
|
||||
quickAdjustmentDTO,
|
||||
}: IInventoryAdjustmentCreatingPayload) {
|
||||
|
||||
@@ -17,7 +17,7 @@ export class InvoiceBranchValidateSubscriber {
|
||||
* Validate branch existance on invoice creating.
|
||||
* @param {ISaleInvoiceCreatingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.saleInvoice.onCreating)
|
||||
@OnEvent(events.saleInvoice.onCreating, { suppressErrors: false })
|
||||
async validateBranchExistanceOnInvoiceCreating({
|
||||
saleInvoiceDTO,
|
||||
}: ISaleInvoiceCreatingPaylaod) {
|
||||
@@ -30,7 +30,7 @@ export class InvoiceBranchValidateSubscriber {
|
||||
* Validate branch existance once invoice editing.
|
||||
* @param {ISaleInvoiceEditingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.saleInvoice.onEditing)
|
||||
@OnEvent(events.saleInvoice.onEditing, { suppressErrors: false })
|
||||
async validateBranchExistanceOnInvoiceEditing({
|
||||
saleInvoiceDTO,
|
||||
}: ISaleInvoiceEditingPayload) {
|
||||
|
||||
@@ -17,7 +17,7 @@ export class PaymentMadeBranchValidateSubscriber {
|
||||
* Validate branch existance on estimate creating.
|
||||
* @param {ISaleEstimateCreatedPayload} payload
|
||||
*/
|
||||
@OnEvent(events.billPayment.onCreating)
|
||||
@OnEvent(events.billPayment.onCreating, { suppressErrors: false })
|
||||
async validateBranchExistanceOnPaymentCreating({
|
||||
billPaymentDTO,
|
||||
}: IBillPaymentCreatingPayload) {
|
||||
@@ -30,7 +30,7 @@ export class PaymentMadeBranchValidateSubscriber {
|
||||
* Validate branch existance once estimate editing.
|
||||
* @param {ISaleEstimateEditingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.billPayment.onEditing)
|
||||
@OnEvent(events.billPayment.onEditing, { suppressErrors: false })
|
||||
async validateBranchExistanceOnPaymentEditing({
|
||||
billPaymentDTO,
|
||||
}: IBillPaymentEditingPayload) {
|
||||
|
||||
@@ -17,7 +17,7 @@ export class PaymentReceiveBranchValidateSubscriber {
|
||||
* Validate branch existance on estimate creating.
|
||||
* @param {IPaymentReceivedCreatingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.paymentReceive.onCreating)
|
||||
@OnEvent(events.paymentReceive.onCreating, { suppressErrors: false })
|
||||
async validateBranchExistanceOnPaymentCreating({
|
||||
paymentReceiveDTO,
|
||||
}: IPaymentReceivedCreatingPayload) {
|
||||
@@ -30,7 +30,7 @@ export class PaymentReceiveBranchValidateSubscriber {
|
||||
* Validate branch existance once estimate editing.
|
||||
* @param {IPaymentReceivedEditingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.paymentReceive.onEditing)
|
||||
@OnEvent(events.paymentReceive.onEditing, { suppressErrors: false })
|
||||
async validateBranchExistanceOnPaymentEditing({
|
||||
paymentReceiveDTO,
|
||||
}: IPaymentReceivedEditingPayload) {
|
||||
|
||||
@@ -17,7 +17,7 @@ export class SaleEstimateBranchValidateSubscriber {
|
||||
* Validate branch existance on estimate creating.
|
||||
* @param {ISaleEstimateCreatedPayload} payload
|
||||
*/
|
||||
@OnEvent(events.saleEstimate.onCreating)
|
||||
@OnEvent(events.saleEstimate.onCreating, { suppressErrors: false })
|
||||
async validateBranchExistanceOnEstimateCreating({
|
||||
estimateDTO,
|
||||
}: ISaleEstimateCreatingPayload) {
|
||||
@@ -30,7 +30,7 @@ export class SaleEstimateBranchValidateSubscriber {
|
||||
* Validate branch existance once estimate editing.
|
||||
* @param {ISaleEstimateEditingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.saleEstimate.onEditing)
|
||||
@OnEvent(events.saleEstimate.onEditing, { suppressErrors: false })
|
||||
async validateBranchExistanceOnEstimateEditing({
|
||||
estimateDTO,
|
||||
}: ISaleEstimateEditingPayload) {
|
||||
|
||||
@@ -17,7 +17,7 @@ export class SaleReceiptBranchValidateSubscriber {
|
||||
* Validate branch existance on estimate creating.
|
||||
* @param {ISaleReceiptCreatingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.saleReceipt.onCreating)
|
||||
@OnEvent(events.saleReceipt.onCreating, { suppressErrors: false })
|
||||
async validateBranchExistanceOnInvoiceCreating({
|
||||
saleReceiptDTO,
|
||||
}: ISaleReceiptCreatingPayload) {
|
||||
@@ -30,7 +30,7 @@ export class SaleReceiptBranchValidateSubscriber {
|
||||
* Validate branch existance once estimate editing.
|
||||
* @param {ISaleReceiptEditingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.saleReceipt.onEditing)
|
||||
@OnEvent(events.saleReceipt.onEditing, { suppressErrors: false })
|
||||
async validateBranchExistanceOnInvoiceEditing({
|
||||
saleReceiptDTO,
|
||||
}: ISaleReceiptEditingPayload) {
|
||||
|
||||
@@ -17,7 +17,7 @@ export class VendorCreditBranchValidateSubscriber {
|
||||
* Validate branch existance on estimate creating.
|
||||
* @param {ISaleEstimateCreatedPayload} payload
|
||||
*/
|
||||
@OnEvent(events.vendorCredit.onCreating)
|
||||
@OnEvent(events.vendorCredit.onCreating, { suppressErrors: false })
|
||||
async validateBranchExistanceOnCreditCreating({
|
||||
vendorCreditCreateDTO,
|
||||
}: IVendorCreditCreatingPayload) {
|
||||
@@ -30,7 +30,7 @@ export class VendorCreditBranchValidateSubscriber {
|
||||
* Validate branch existance once estimate editing.
|
||||
* @param {ISaleEstimateEditingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.vendorCredit.onEditing)
|
||||
@OnEvent(events.vendorCredit.onEditing, { suppressErrors: false })
|
||||
async validateBranchExistanceOnCreditEditing({
|
||||
vendorCreditDTO,
|
||||
}: IVendorCreditEditingPayload) {
|
||||
|
||||
@@ -14,7 +14,7 @@ export class VendorCreditRefundBranchValidateSubscriber {
|
||||
* Validate branch existance on refund credit note creating.
|
||||
* @param {IRefundVendorCreditCreatingPayload} payload
|
||||
*/
|
||||
@OnEvent(events.vendorCredit.onRefundCreating)
|
||||
@OnEvent(events.vendorCredit.onRefundCreating, { suppressErrors: false })
|
||||
async validateBranchExistanceOnCreditRefundCreating({
|
||||
refundVendorCreditDTO,
|
||||
}: IRefundVendorCreditCreatingPayload) {
|
||||
|
||||
@@ -22,6 +22,7 @@ export abstract class BaseCommand extends CommandRunner {
|
||||
},
|
||||
migrations: {
|
||||
directory: this.configService.get('systemDatabase.migrationDir'),
|
||||
loadExtensions: ['.js'],
|
||||
},
|
||||
seeds: {
|
||||
directory: this.configService.get('systemDatabase.seedsDir'),
|
||||
@@ -43,6 +44,7 @@ export abstract class BaseCommand extends CommandRunner {
|
||||
},
|
||||
migrations: {
|
||||
directory: this.configService.get('tenantDatabase.migrationsDir') || './src/database/migrations',
|
||||
loadExtensions: ['.js'],
|
||||
},
|
||||
seeds: {
|
||||
directory: this.configService.get('tenantDatabase.seedsDir') || './src/database/seeds/core',
|
||||
|
||||
@@ -1,117 +1,103 @@
|
||||
// import { Service, Inject } from 'typedi';
|
||||
// import { AccountNormal, ICustomer, ILedgerEntry } from '@/interfaces';
|
||||
// import Ledger from '@/services/Accounting/Ledger';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AccountNormal } from '@/interfaces/Account';
|
||||
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
|
||||
import { Ledger } from '@/modules/Ledger/Ledger';
|
||||
import { Customer } from './models/Customer';
|
||||
|
||||
// @Service()
|
||||
// export class CustomerGLEntries {
|
||||
// /**
|
||||
// * Retrieves the customer opening balance common entry attributes.
|
||||
// * @param {ICustomer} customer
|
||||
// */
|
||||
// private getCustomerOpeningGLCommonEntry = (customer: ICustomer) => {
|
||||
// return {
|
||||
// exchangeRate: customer.openingBalanceExchangeRate,
|
||||
// currencyCode: customer.currencyCode,
|
||||
@Injectable()
|
||||
export class CustomerGLEntries {
|
||||
/**
|
||||
* Retrieves the customer opening balance common entry attributes.
|
||||
*/
|
||||
private getCustomerOpeningGLCommonEntry = (customer: Customer) => {
|
||||
return {
|
||||
exchangeRate: customer.openingBalanceExchangeRate,
|
||||
currencyCode: customer.currencyCode,
|
||||
|
||||
// transactionType: 'CustomerOpeningBalance',
|
||||
// transactionId: customer.id,
|
||||
transactionType: 'CustomerOpeningBalance',
|
||||
transactionId: customer.id,
|
||||
|
||||
// date: customer.openingBalanceAt,
|
||||
// userId: customer.userId,
|
||||
// contactId: customer.id,
|
||||
date: customer.openingBalanceAt,
|
||||
contactId: customer.id,
|
||||
|
||||
// credit: 0,
|
||||
// debit: 0,
|
||||
credit: 0,
|
||||
debit: 0,
|
||||
|
||||
// branchId: customer.openingBalanceBranchId,
|
||||
// };
|
||||
// };
|
||||
branchId: customer.openingBalanceBranchId,
|
||||
};
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Retrieves the customer opening GL credit entry.
|
||||
// * @param {number} ARAccountId
|
||||
// * @param {ICustomer} customer
|
||||
// * @returns {ILedgerEntry}
|
||||
// */
|
||||
// private getCustomerOpeningGLCreditEntry = (
|
||||
// ARAccountId: number,
|
||||
// customer: ICustomer
|
||||
// ): ILedgerEntry => {
|
||||
// const commonEntry = this.getCustomerOpeningGLCommonEntry(customer);
|
||||
/**
|
||||
* Retrieves the customer opening GL credit entry.
|
||||
*/
|
||||
private getCustomerOpeningGLCreditEntry = (
|
||||
ARAccountId: number,
|
||||
customer: Customer
|
||||
): ILedgerEntry => {
|
||||
const commonEntry = this.getCustomerOpeningGLCommonEntry(customer);
|
||||
|
||||
// return {
|
||||
// ...commonEntry,
|
||||
// credit: 0,
|
||||
// debit: customer.localOpeningBalance,
|
||||
// accountId: ARAccountId,
|
||||
// accountNormal: AccountNormal.DEBIT,
|
||||
// index: 1,
|
||||
// };
|
||||
// };
|
||||
return {
|
||||
...commonEntry,
|
||||
credit: 0,
|
||||
debit: customer.localOpeningBalance,
|
||||
accountId: ARAccountId,
|
||||
accountNormal: AccountNormal.DEBIT,
|
||||
index: 1,
|
||||
};
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Retrieves the customer opening GL debit entry.
|
||||
// * @param {number} incomeAccountId
|
||||
// * @param {ICustomer} customer
|
||||
// * @returns {ILedgerEntry}
|
||||
// */
|
||||
// private getCustomerOpeningGLDebitEntry = (
|
||||
// incomeAccountId: number,
|
||||
// customer: ICustomer
|
||||
// ): ILedgerEntry => {
|
||||
// const commonEntry = this.getCustomerOpeningGLCommonEntry(customer);
|
||||
/**
|
||||
* Retrieves the customer opening GL debit entry.
|
||||
*/
|
||||
private getCustomerOpeningGLDebitEntry = (
|
||||
incomeAccountId: number,
|
||||
customer: Customer
|
||||
): ILedgerEntry => {
|
||||
const commonEntry = this.getCustomerOpeningGLCommonEntry(customer);
|
||||
|
||||
// return {
|
||||
// ...commonEntry,
|
||||
// credit: customer.localOpeningBalance,
|
||||
// debit: 0,
|
||||
// accountId: incomeAccountId,
|
||||
// accountNormal: AccountNormal.CREDIT,
|
||||
return {
|
||||
...commonEntry,
|
||||
credit: customer.localOpeningBalance,
|
||||
debit: 0,
|
||||
accountId: incomeAccountId,
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
|
||||
// index: 2,
|
||||
// };
|
||||
// };
|
||||
index: 2,
|
||||
};
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Retrieves the customer opening GL entries.
|
||||
// * @param {number} ARAccountId
|
||||
// * @param {number} incomeAccountId
|
||||
// * @param {ICustomer} customer
|
||||
// * @returns {ILedgerEntry[]}
|
||||
// */
|
||||
// public getCustomerOpeningGLEntries = (
|
||||
// ARAccountId: number,
|
||||
// incomeAccountId: number,
|
||||
// customer: ICustomer
|
||||
// ) => {
|
||||
// const debitEntry = this.getCustomerOpeningGLDebitEntry(
|
||||
// incomeAccountId,
|
||||
// customer
|
||||
// );
|
||||
// const creditEntry = this.getCustomerOpeningGLCreditEntry(
|
||||
// ARAccountId,
|
||||
// customer
|
||||
// );
|
||||
// return [debitEntry, creditEntry];
|
||||
// };
|
||||
/**
|
||||
* Retrieves the customer opening GL entries.
|
||||
*/
|
||||
public getCustomerOpeningGLEntries = (
|
||||
ARAccountId: number,
|
||||
incomeAccountId: number,
|
||||
customer: Customer
|
||||
) => {
|
||||
const debitEntry = this.getCustomerOpeningGLDebitEntry(
|
||||
incomeAccountId,
|
||||
customer
|
||||
);
|
||||
const creditEntry = this.getCustomerOpeningGLCreditEntry(
|
||||
ARAccountId,
|
||||
customer
|
||||
);
|
||||
return [debitEntry, creditEntry];
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Retrieves the customer opening balance ledger.
|
||||
// * @param {number} ARAccountId
|
||||
// * @param {number} incomeAccountId
|
||||
// * @param {ICustomer} customer
|
||||
// * @returns {ILedger}
|
||||
// */
|
||||
// public getCustomerOpeningLedger = (
|
||||
// ARAccountId: number,
|
||||
// incomeAccountId: number,
|
||||
// customer: ICustomer
|
||||
// ) => {
|
||||
// const entries = this.getCustomerOpeningGLEntries(
|
||||
// ARAccountId,
|
||||
// incomeAccountId,
|
||||
// customer
|
||||
// );
|
||||
// return new Ledger(entries);
|
||||
// };
|
||||
// }
|
||||
/**
|
||||
* Retrieves the customer opening balance ledger.
|
||||
*/
|
||||
public getCustomerOpeningLedger = (
|
||||
ARAccountId: number,
|
||||
incomeAccountId: number,
|
||||
customer: Customer
|
||||
) => {
|
||||
const entries = this.getCustomerOpeningGLEntries(
|
||||
ARAccountId,
|
||||
incomeAccountId,
|
||||
customer
|
||||
);
|
||||
return new Ledger(entries);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,90 +1,84 @@
|
||||
// import { Knex } from 'knex';
|
||||
// import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
||||
// import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
// import { Service, Inject } from 'typedi';
|
||||
// import { CustomerGLEntries } from './CustomerGLEntries';
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service';
|
||||
import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository';
|
||||
import { CustomerGLEntries } from './CustomerGLEntries';
|
||||
import { Customer } from './models/Customer';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
import { Account } from '../Accounts/models/Account.model';
|
||||
|
||||
// @Service()
|
||||
// export class CustomerGLEntriesStorage {
|
||||
// @Inject()
|
||||
// private tenancy: HasTenancyService;
|
||||
@Injectable()
|
||||
export class CustomerGLEntriesStorage {
|
||||
constructor(
|
||||
private readonly ledgerStorage: LedgerStorageService,
|
||||
private readonly accountRepository: AccountRepository,
|
||||
private readonly customerGLEntries: CustomerGLEntries,
|
||||
|
||||
// @Inject()
|
||||
// private ledegrRepository: LedgerStorageService;
|
||||
@Inject(Account.name)
|
||||
private readonly accountModel: TenantModelProxy<typeof Account>,
|
||||
|
||||
// @Inject()
|
||||
// private customerGLEntries: CustomerGLEntries;
|
||||
@Inject(Customer.name)
|
||||
private readonly customerModel: TenantModelProxy<typeof Customer>,
|
||||
) { }
|
||||
|
||||
// /**
|
||||
// * Customer opening balance journals.
|
||||
// * @param {number} tenantId
|
||||
// * @param {number} customerId
|
||||
// * @param {Knex.Transaction} trx
|
||||
// */
|
||||
// public writeCustomerOpeningBalance = async (
|
||||
// tenantId: number,
|
||||
// customerId: number,
|
||||
// trx?: Knex.Transaction
|
||||
// ) => {
|
||||
// const { Customer } = this.tenancy.models(tenantId);
|
||||
// const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
/**
|
||||
* Customer opening balance journals.
|
||||
*/
|
||||
public writeCustomerOpeningBalance = async (
|
||||
customerId: number,
|
||||
trx?: Knex.Transaction,
|
||||
) => {
|
||||
const customer = await this.customerModel()
|
||||
.query(trx)
|
||||
.findById(customerId);
|
||||
|
||||
// const customer = await Customer.query(trx).findById(customerId);
|
||||
// Finds the income account.
|
||||
const incomeAccount = await this.accountModel()
|
||||
.query(trx)
|
||||
.findOne({ slug: 'other-income' });
|
||||
|
||||
// // Finds the income account.
|
||||
// const incomeAccount = await accountRepository.findOne({
|
||||
// slug: 'other-income',
|
||||
// });
|
||||
// // Find or create the A/R account.
|
||||
// const ARAccount = await accountRepository.findOrCreateAccountReceivable(
|
||||
// customer.currencyCode,
|
||||
// {},
|
||||
// trx
|
||||
// );
|
||||
// // Retrieves the customer opening balance ledger.
|
||||
// const ledger = this.customerGLEntries.getCustomerOpeningLedger(
|
||||
// ARAccount.id,
|
||||
// incomeAccount.id,
|
||||
// customer
|
||||
// );
|
||||
// // Commits the ledger entries to the storage.
|
||||
// await this.ledegrRepository.commit(tenantId, ledger, trx);
|
||||
// };
|
||||
// Find or create the A/R account.
|
||||
const ARAccount =
|
||||
await this.accountRepository.findOrCreateAccountReceivable(
|
||||
customer.currencyCode,
|
||||
{},
|
||||
trx,
|
||||
);
|
||||
// Retrieves the customer opening balance ledger.
|
||||
const ledger = this.customerGLEntries.getCustomerOpeningLedger(
|
||||
ARAccount.id,
|
||||
incomeAccount.id,
|
||||
customer,
|
||||
);
|
||||
// Commits the ledger entries to the storage.
|
||||
await this.ledgerStorage.commit(ledger, trx);
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Reverts the customer opening balance GL entries.
|
||||
// * @param {number} tenantId
|
||||
// * @param {number} customerId
|
||||
// * @param {Knex.Transaction} trx
|
||||
// */
|
||||
// public revertCustomerOpeningBalance = async (
|
||||
// tenantId: number,
|
||||
// customerId: number,
|
||||
// trx?: Knex.Transaction
|
||||
// ) => {
|
||||
// await this.ledegrRepository.deleteByReference(
|
||||
// tenantId,
|
||||
// customerId,
|
||||
// 'CustomerOpeningBalance',
|
||||
// trx
|
||||
// );
|
||||
// };
|
||||
/**
|
||||
* Reverts the customer opening balance GL entries.
|
||||
*/
|
||||
public revertCustomerOpeningBalance = async (
|
||||
customerId: number,
|
||||
trx?: Knex.Transaction,
|
||||
) => {
|
||||
await this.ledgerStorage.deleteByReference(
|
||||
customerId,
|
||||
'CustomerOpeningBalance',
|
||||
trx,
|
||||
);
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Writes the customer opening balance GL entries.
|
||||
// * @param {number} tenantId
|
||||
// * @param {number} customerId
|
||||
// * @param {Knex.Transaction} trx
|
||||
// */
|
||||
// public rewriteCustomerOpeningBalance = async (
|
||||
// tenantId: number,
|
||||
// customerId: number,
|
||||
// trx?: Knex.Transaction
|
||||
// ) => {
|
||||
// // Reverts the customer opening balance entries.
|
||||
// await this.revertCustomerOpeningBalance(tenantId, customerId, trx);
|
||||
/**
|
||||
* Writes the customer opening balance GL entries.
|
||||
*/
|
||||
public rewriteCustomerOpeningBalance = async (
|
||||
customerId: number,
|
||||
trx?: Knex.Transaction,
|
||||
) => {
|
||||
// Reverts the customer opening balance entries.
|
||||
await this.revertCustomerOpeningBalance(customerId, trx);
|
||||
|
||||
// // Write the customer opening balance entries.
|
||||
// await this.writeCustomerOpeningBalance(tenantId, customerId, trx);
|
||||
// };
|
||||
// }
|
||||
// Write the customer opening balance entries.
|
||||
await this.writeCustomerOpeningBalance(customerId, trx);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,10 +9,7 @@ import {
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { CustomersApplication } from './CustomersApplication.service';
|
||||
import {
|
||||
ICustomerOpeningBalanceEditDTO,
|
||||
ICustomersFilter,
|
||||
} from './types/Customers.types';
|
||||
import { CustomerOpeningBalanceEditDto } from './dtos/CustomerOpeningBalanceEdit.dto';
|
||||
import {
|
||||
ApiOperation,
|
||||
ApiResponse,
|
||||
@@ -106,7 +103,7 @@ export class CustomersController {
|
||||
})
|
||||
editOpeningBalance(
|
||||
@Param('id') customerId: number,
|
||||
@Body() openingBalanceDTO: ICustomerOpeningBalanceEditDTO,
|
||||
@Body() openingBalanceDTO: CustomerOpeningBalanceEditDto,
|
||||
) {
|
||||
return this.customersApplication.editOpeningBalance(
|
||||
customerId,
|
||||
|
||||
@@ -18,9 +18,19 @@ import { GetCustomers } from './queries/GetCustomers.service';
|
||||
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
|
||||
import { BulkDeleteCustomersService } from './BulkDeleteCustomers.service';
|
||||
import { ValidateBulkDeleteCustomersService } from './ValidateBulkDeleteCustomers.service';
|
||||
import { LedgerModule } from '../Ledger/Ledger.module';
|
||||
import { AccountsModule } from '../Accounts/Accounts.module';
|
||||
import { CustomerGLEntries } from './CustomerGLEntries';
|
||||
import { CustomerGLEntriesStorage } from './CustomerGLEntriesStorage';
|
||||
import { CustomerWriteGLOpeningBalanceSubscriber } from './subscribers/CustomerGLEntriesSubscriber';
|
||||
|
||||
@Module({
|
||||
imports: [TenancyDatabaseModule, DynamicListModule],
|
||||
imports: [
|
||||
TenancyDatabaseModule,
|
||||
DynamicListModule,
|
||||
LedgerModule,
|
||||
AccountsModule,
|
||||
],
|
||||
controllers: [CustomersController],
|
||||
providers: [
|
||||
ActivateCustomer,
|
||||
@@ -41,6 +51,9 @@ import { ValidateBulkDeleteCustomersService } from './ValidateBulkDeleteCustomer
|
||||
GetCustomers,
|
||||
BulkDeleteCustomersService,
|
||||
ValidateBulkDeleteCustomersService,
|
||||
CustomerGLEntries,
|
||||
CustomerGLEntriesStorage,
|
||||
CustomerWriteGLOpeningBalanceSubscriber,
|
||||
],
|
||||
})
|
||||
export class CustomersModule {}
|
||||
|
||||
@@ -4,10 +4,7 @@ import { CreateCustomer } from './commands/CreateCustomer.service';
|
||||
import { EditCustomer } from './commands/EditCustomer.service';
|
||||
import { DeleteCustomer } from './commands/DeleteCustomer.service';
|
||||
import { EditOpeningBalanceCustomer } from './commands/EditOpeningBalanceCustomer.service';
|
||||
import {
|
||||
ICustomerOpeningBalanceEditDTO,
|
||||
ICustomersFilter,
|
||||
} from './types/Customers.types';
|
||||
import { CustomerOpeningBalanceEditDto } from './dtos/CustomerOpeningBalanceEdit.dto';
|
||||
import { CreateCustomerDto } from './dtos/CreateCustomer.dto';
|
||||
import { EditCustomerDto } from './dtos/EditCustomer.dto';
|
||||
import { GetCustomers } from './queries/GetCustomers.service';
|
||||
@@ -18,12 +15,12 @@ import { ValidateBulkDeleteCustomersService } from './ValidateBulkDeleteCustomer
|
||||
@Injectable()
|
||||
export class CustomersApplication {
|
||||
constructor(
|
||||
private getCustomerService: GetCustomerService,
|
||||
private createCustomerService: CreateCustomer,
|
||||
private editCustomerService: EditCustomer,
|
||||
private deleteCustomerService: DeleteCustomer,
|
||||
private editOpeningBalanceService: EditOpeningBalanceCustomer,
|
||||
private getCustomersService: GetCustomers,
|
||||
private readonly getCustomerService: GetCustomerService,
|
||||
private readonly createCustomerService: CreateCustomer,
|
||||
private readonly editCustomerService: EditCustomer,
|
||||
private readonly deleteCustomerService: DeleteCustomer,
|
||||
private readonly editOpeningBalanceService: EditOpeningBalanceCustomer,
|
||||
private readonly getCustomersService: GetCustomers,
|
||||
private readonly bulkDeleteCustomersService: BulkDeleteCustomersService,
|
||||
private readonly validateBulkDeleteCustomersService: ValidateBulkDeleteCustomersService,
|
||||
) {}
|
||||
@@ -72,7 +69,7 @@ export class CustomersApplication {
|
||||
*/
|
||||
public editOpeningBalance = (
|
||||
customerId: number,
|
||||
openingBalanceEditDTO: ICustomerOpeningBalanceEditDTO,
|
||||
openingBalanceEditDTO: CustomerOpeningBalanceEditDto,
|
||||
) => {
|
||||
return this.editOpeningBalanceService.changeOpeningBalance(
|
||||
customerId,
|
||||
@@ -82,7 +79,7 @@ export class CustomersApplication {
|
||||
|
||||
/**
|
||||
* Retrieve customers paginated list.
|
||||
* @param {ICustomersFilter} filter - Cusotmers filter.
|
||||
* @param {GetCustomersQueryDto} filter - Cusotmers filter.
|
||||
*/
|
||||
public getCustomers = (filterDTO: GetCustomersQueryDto) => {
|
||||
return this.getCustomersService.getCustomersList(filterDTO);
|
||||
|
||||
@@ -31,7 +31,7 @@ export class CreateCustomer {
|
||||
|
||||
/**
|
||||
* Creates a new customer.
|
||||
* @param {ICustomerNewDTO} customerDTO
|
||||
* @param {CreateCustomerDto} customerDTO
|
||||
* @return {Promise<ICustomer>}
|
||||
*/
|
||||
public async createCustomer(
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Knex } from 'knex';
|
||||
import {
|
||||
ICustomerOpeningBalanceEditDTO,
|
||||
ICustomerOpeningBalanceEditedPayload,
|
||||
ICustomerOpeningBalanceEditingPayload,
|
||||
} from '../types/Customers.types';
|
||||
import { CustomerOpeningBalanceEditDto } from '../dtos/CustomerOpeningBalanceEdit.dto';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { Customer } from '../models/Customer';
|
||||
@@ -29,11 +29,11 @@ export class EditOpeningBalanceCustomer {
|
||||
/**
|
||||
* Changes the opening balance of the given customer.
|
||||
* @param {number} customerId - Customer ID.
|
||||
* @param {ICustomerOpeningBalanceEditDTO} openingBalanceEditDTO
|
||||
* @param {CustomerOpeningBalanceEditDto} openingBalanceEditDTO
|
||||
*/
|
||||
public async changeOpeningBalance(
|
||||
customerId: number,
|
||||
openingBalanceEditDTO: ICustomerOpeningBalanceEditDTO,
|
||||
openingBalanceEditDTO: CustomerOpeningBalanceEditDto,
|
||||
): Promise<Customer> {
|
||||
// Retrieves the old customer or throw not found error.
|
||||
const oldCustomer = await this.customerModel()
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsString,
|
||||
ValidateIf,
|
||||
} from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsOptional, ToNumber } from '@/common/decorators/Validators';
|
||||
@@ -40,10 +41,11 @@ export class CreateCustomerDto extends ContactAddressDto {
|
||||
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
description: 'Opening balance date',
|
||||
description: 'Opening balance date (required when openingBalance is provided)',
|
||||
example: '2024-01-01',
|
||||
})
|
||||
@IsOptional()
|
||||
@ValidateIf((o) => o.openingBalance != null)
|
||||
@IsNotEmpty({ message: 'openingBalanceAt is required when openingBalance is provided' })
|
||||
@IsString()
|
||||
openingBalanceAt?: string;
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsOptional, ToNumber } from '@/common/decorators/Validators';
|
||||
|
||||
export class CustomerOpeningBalanceEditDto {
|
||||
@ApiProperty({
|
||||
required: true,
|
||||
description: 'Opening balance',
|
||||
example: 5000.0,
|
||||
})
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
@ToNumber()
|
||||
openingBalance: number;
|
||||
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
description: 'Opening balance date',
|
||||
example: '2024-01-01',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
openingBalanceAt?: string;
|
||||
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
description: 'Opening balance exchange rate',
|
||||
example: 1.0,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@ToNumber()
|
||||
openingBalanceExchangeRate?: number;
|
||||
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
description: 'Opening balance branch ID',
|
||||
example: 101,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@ToNumber()
|
||||
openingBalanceBranchId?: number;
|
||||
}
|
||||
@@ -1,91 +1,63 @@
|
||||
// import { Service, Inject } from 'typedi';
|
||||
// import {
|
||||
// ICustomerEventCreatedPayload,
|
||||
// ICustomerEventDeletedPayload,
|
||||
// ICustomerOpeningBalanceEditedPayload,
|
||||
// } from '@/interfaces';
|
||||
// import events from '@/subscribers/events';
|
||||
// import { CustomerGLEntriesStorage } from '../CustomerGLEntriesStorage';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import {
|
||||
ICustomerEventCreatedPayload,
|
||||
ICustomerEventDeletedPayload,
|
||||
ICustomerOpeningBalanceEditedPayload,
|
||||
} from '../types/Customers.types';
|
||||
import { events } from '@/common/events/events';
|
||||
import { CustomerGLEntriesStorage } from '../CustomerGLEntriesStorage';
|
||||
|
||||
// @Service()
|
||||
// export class CustomerWriteGLOpeningBalanceSubscriber {
|
||||
// @Inject()
|
||||
// private customerGLEntries: CustomerGLEntriesStorage;
|
||||
@Injectable()
|
||||
export class CustomerWriteGLOpeningBalanceSubscriber {
|
||||
constructor(private readonly customerGLEntries: CustomerGLEntriesStorage) { }
|
||||
|
||||
// /**
|
||||
// * Attaches events with handlers.
|
||||
// */
|
||||
// public attach(bus) {
|
||||
// bus.subscribe(
|
||||
// events.customers.onCreated,
|
||||
// this.handleWriteOpenBalanceEntries
|
||||
// );
|
||||
// bus.subscribe(
|
||||
// events.customers.onDeleted,
|
||||
// this.handleRevertOpeningBalanceEntries
|
||||
// );
|
||||
// bus.subscribe(
|
||||
// events.customers.onOpeningBalanceChanged,
|
||||
// this.handleRewriteOpeningEntriesOnChanged
|
||||
// );
|
||||
// }
|
||||
/**
|
||||
* Handles the writing opening balance journal entries once the customer created.
|
||||
*/
|
||||
@OnEvent(events.customers.onCreated)
|
||||
public async handleWriteOpenBalanceEntries({
|
||||
customer,
|
||||
trx,
|
||||
}: ICustomerEventCreatedPayload) {
|
||||
// Writes the customer opening balance journal entries.
|
||||
if (customer.openingBalance) {
|
||||
await this.customerGLEntries.writeCustomerOpeningBalance(
|
||||
customer.id,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Handles the writing opening balance journal entries once the customer created.
|
||||
// * @param {ICustomerEventCreatedPayload} payload -
|
||||
// */
|
||||
// private handleWriteOpenBalanceEntries = async ({
|
||||
// tenantId,
|
||||
// customer,
|
||||
// trx,
|
||||
// }: ICustomerEventCreatedPayload) => {
|
||||
// // Writes the customer opening balance journal entries.
|
||||
// if (customer.openingBalance) {
|
||||
// await this.customerGLEntries.writeCustomerOpeningBalance(
|
||||
// tenantId,
|
||||
// customer.id,
|
||||
// trx
|
||||
// );
|
||||
// }
|
||||
// };
|
||||
/**
|
||||
* Handles the deleting opening balance journal entries once the customer deleted.
|
||||
*/
|
||||
@OnEvent(events.customers.onDeleted)
|
||||
public async handleRevertOpeningBalanceEntries({
|
||||
customerId,
|
||||
trx,
|
||||
}: ICustomerEventDeletedPayload) {
|
||||
await this.customerGLEntries.revertCustomerOpeningBalance(customerId, trx);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Handles the deleting opeing balance journal entrise once the customer deleted.
|
||||
// * @param {ICustomerEventDeletedPayload} payload -
|
||||
// */
|
||||
// private handleRevertOpeningBalanceEntries = async ({
|
||||
// tenantId,
|
||||
// customerId,
|
||||
// trx,
|
||||
// }: ICustomerEventDeletedPayload) => {
|
||||
// await this.customerGLEntries.revertCustomerOpeningBalance(
|
||||
// tenantId,
|
||||
// customerId,
|
||||
// trx
|
||||
// );
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Handles the rewrite opening balance entries once opening balnace changed.
|
||||
// * @param {ICustomerOpeningBalanceEditedPayload} payload -
|
||||
// */
|
||||
// private handleRewriteOpeningEntriesOnChanged = async ({
|
||||
// tenantId,
|
||||
// customer,
|
||||
// trx,
|
||||
// }: ICustomerOpeningBalanceEditedPayload) => {
|
||||
// if (customer.openingBalance) {
|
||||
// await this.customerGLEntries.rewriteCustomerOpeningBalance(
|
||||
// tenantId,
|
||||
// customer.id,
|
||||
// trx
|
||||
// );
|
||||
// } else {
|
||||
// await this.customerGLEntries.revertCustomerOpeningBalance(
|
||||
// tenantId,
|
||||
// customer.id,
|
||||
// trx
|
||||
// );
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
/**
|
||||
* Handles the rewrite opening balance entries once opening balance changed.
|
||||
*/
|
||||
@OnEvent(events.customers.onOpeningBalanceChanged)
|
||||
public async handleRewriteOpeningEntriesOnChanged({
|
||||
customer,
|
||||
trx,
|
||||
}: ICustomerOpeningBalanceEditedPayload) {
|
||||
if (customer.openingBalance) {
|
||||
await this.customerGLEntries.rewriteCustomerOpeningBalance(
|
||||
customer.id,
|
||||
trx,
|
||||
);
|
||||
} else {
|
||||
await this.customerGLEntries.revertCustomerOpeningBalance(
|
||||
customer.id,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { IContactAddressDTO } from '@/modules/Contacts/types/Contacts.types';
|
||||
import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/DynamicFilter.types';
|
||||
import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model';
|
||||
import { CreateCustomerDto } from '../dtos/CreateCustomer.dto';
|
||||
import { CustomerOpeningBalanceEditDto } from '../dtos/CustomerOpeningBalanceEdit.dto';
|
||||
import { EditCustomerDto } from '../dtos/EditCustomer.dto';
|
||||
|
||||
// Customer Interfaces.
|
||||
@@ -113,23 +114,16 @@ export enum VendorAction {
|
||||
View = 'View',
|
||||
}
|
||||
|
||||
export interface ICustomerOpeningBalanceEditDTO {
|
||||
openingBalance: number;
|
||||
openingBalanceAt: Date | string;
|
||||
openingBalanceExchangeRate: number;
|
||||
openingBalanceBranchId?: number;
|
||||
}
|
||||
|
||||
export interface ICustomerOpeningBalanceEditingPayload {
|
||||
oldCustomer: Customer;
|
||||
openingBalanceEditDTO: ICustomerOpeningBalanceEditDTO;
|
||||
openingBalanceEditDTO: CustomerOpeningBalanceEditDto;
|
||||
trx?: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface ICustomerOpeningBalanceEditedPayload {
|
||||
customer: Customer;
|
||||
oldCustomer: Customer;
|
||||
openingBalanceEditDTO: ICustomerOpeningBalanceEditDTO;
|
||||
openingBalanceEditDTO: CustomerOpeningBalanceEditDto;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,15 @@ import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||
import { TableSheetPdf } from './TableSheetPdf';
|
||||
import { TemplateInjectableModule } from '@/modules/TemplateInjectable/TemplateInjectable.module';
|
||||
import { ChromiumlyTenancyModule } from '@/modules/ChromiumlyTenancy/ChromiumlyTenancy.module';
|
||||
import { InventoryCostModule } from '@/modules/InventoryCost/InventoryCost.module';
|
||||
|
||||
@Module({
|
||||
imports: [TemplateInjectableModule, ChromiumlyTenancyModule],
|
||||
providers: [
|
||||
FinancialSheetMeta,
|
||||
TenancyContext,
|
||||
TableSheetPdf,
|
||||
imports: [
|
||||
TemplateInjectableModule,
|
||||
ChromiumlyTenancyModule,
|
||||
InventoryCostModule,
|
||||
],
|
||||
providers: [FinancialSheetMeta, TenancyContext, TableSheetPdf],
|
||||
exports: [FinancialSheetMeta, TableSheetPdf],
|
||||
})
|
||||
export class FinancialSheetCommonModule {}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { IFinancialSheetCommonMeta } from '../types/Report.types';
|
||||
import { TenancyContext } from '@/modules/Tenancy/TenancyContext.service';
|
||||
import { InventoryComputeCostService } from '@/modules/InventoryCost/commands/InventoryComputeCost.service';
|
||||
|
||||
@Injectable()
|
||||
export class FinancialSheetMeta {
|
||||
constructor(private readonly tenancyContext: TenancyContext) {}
|
||||
constructor(
|
||||
private readonly tenancyContext: TenancyContext,
|
||||
private readonly inventoryComputeCostService: InventoryComputeCostService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Retrieves the common meta data of the financial sheet.
|
||||
@@ -17,10 +21,8 @@ export class FinancialSheetMeta {
|
||||
const baseCurrency = tenantMetadata.baseCurrency;
|
||||
const dateFormat = tenantMetadata.dateFormat;
|
||||
|
||||
// const isCostComputeRunning =
|
||||
// this.inventoryService.isItemsCostComputeRunning();
|
||||
|
||||
const isCostComputeRunning = false;
|
||||
const isCostComputeRunning =
|
||||
await this.inventoryComputeCostService.isItemsCostComputeRunning();
|
||||
|
||||
return {
|
||||
organizationName,
|
||||
|
||||
@@ -24,14 +24,14 @@ export class BalanceSheetQueryDto extends FinancialSheetBranchesQueryDto {
|
||||
displayColumnsType: 'total' | 'date_periods' = 'total';
|
||||
|
||||
@ApiProperty({
|
||||
enum: ['day', 'month', 'year'],
|
||||
enum: ['day', 'month', 'year', 'quarter'],
|
||||
default: 'year',
|
||||
description: 'Time period for column display',
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@IsEnum(['day', 'month', 'year'])
|
||||
displayColumnsBy: 'day' | 'month' | 'year' = 'year';
|
||||
@IsEnum(['day', 'month', 'year', 'quarter'])
|
||||
displayColumnsBy: 'day' | 'month' | 'year' | 'quarter' = 'year';
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Start date for the balance sheet period',
|
||||
|
||||
@@ -34,13 +34,13 @@ export class CashFlowStatementQueryDto extends FinancialSheetBranchesQueryDto {
|
||||
@ApiProperty({
|
||||
description: 'Display columns by time period',
|
||||
required: false,
|
||||
enum: ['day', 'month', 'year'],
|
||||
enum: ['day', 'month', 'year', 'quarter'],
|
||||
default: 'year',
|
||||
})
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@IsEnum(['day', 'month', 'year'])
|
||||
displayColumnsBy: 'day' | 'month' | 'year' = 'year';
|
||||
@IsEnum(['day', 'month', 'year', 'quarter'])
|
||||
displayColumnsBy: 'day' | 'month' | 'year' | 'quarter' = 'year';
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Type of column display',
|
||||
|
||||
@@ -64,10 +64,10 @@ export class ProfitLossSheetQueryDto extends FinancialSheetBranchesQueryDto {
|
||||
displayColumnsType: 'total' | 'date_periods';
|
||||
|
||||
@IsString()
|
||||
@IsEnum(['day', 'month', 'year'])
|
||||
@IsEnum(['day', 'month', 'year', 'quarter'])
|
||||
@IsOptional()
|
||||
@ApiProperty({ description: 'How to display columns' })
|
||||
displayColumnsBy: 'day' | 'month' | 'year' = 'year';
|
||||
displayColumnsBy: 'day' | 'month' | 'year' | 'quarter' = 'year';
|
||||
|
||||
@Transform(({ value }) => parseBoolean(value, false))
|
||||
@IsBoolean()
|
||||
|
||||
@@ -16,6 +16,8 @@ import {
|
||||
ComputeItemCostQueue,
|
||||
WriteInventoryTransactionsGLEntriesQueue,
|
||||
} from './types/InventoryCost.types';
|
||||
import { BullBoardModule } from '@bull-board/nestjs';
|
||||
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
|
||||
import { BullModule } from '@nestjs/bullmq';
|
||||
import { InventoryAverageCostMethodService } from './commands/InventoryAverageCostMethod.service';
|
||||
import { InventoryItemCostService } from './commands/InventoryCosts.service';
|
||||
@@ -39,6 +41,14 @@ const models = [
|
||||
BullModule.registerQueue({
|
||||
name: WriteInventoryTransactionsGLEntriesQueue,
|
||||
}),
|
||||
BullBoardModule.forFeature({
|
||||
name: ComputeItemCostQueue,
|
||||
adapter: BullMQAdapter,
|
||||
}),
|
||||
BullBoardModule.forFeature({
|
||||
name: WriteInventoryTransactionsGLEntriesQueue,
|
||||
adapter: BullMQAdapter,
|
||||
}),
|
||||
forwardRef(() => SaleInvoicesModule),
|
||||
ImportModule,
|
||||
],
|
||||
@@ -56,7 +66,7 @@ const models = [
|
||||
InventoryItemCostService,
|
||||
InventoryItemOpeningAvgCostService,
|
||||
InventoryCostSubscriber,
|
||||
GetItemsInventoryValuationListService
|
||||
GetItemsInventoryValuationListService,
|
||||
],
|
||||
exports: [
|
||||
...models,
|
||||
@@ -64,6 +74,6 @@ const models = [
|
||||
InventoryItemCostService,
|
||||
InventoryComputeCostService,
|
||||
],
|
||||
controllers: [InventoryCostController]
|
||||
controllers: [InventoryCostController],
|
||||
})
|
||||
export class InventoryCostModule {}
|
||||
|
||||
@@ -7,11 +7,7 @@ import * as moment from 'moment';
|
||||
import { TenantJobPayload } from '@/interfaces/Tenant';
|
||||
import { InventoryComputeCostService } from '../commands/InventoryComputeCost.service';
|
||||
import { events } from '@/common/events/events';
|
||||
import {
|
||||
ComputeItemCostQueue,
|
||||
ComputeItemCostQueueJob,
|
||||
} from '../types/InventoryCost.types';
|
||||
import { Process } from '@nestjs/bull';
|
||||
import { ComputeItemCostQueue } from '../types/InventoryCost.types';
|
||||
|
||||
interface ComputeItemCostJobPayload extends TenantJobPayload {
|
||||
itemId: number;
|
||||
@@ -39,7 +35,6 @@ export class ComputeItemCostProcessor extends WorkerHost {
|
||||
* Process the compute item cost job.
|
||||
* @param {Job<ComputeItemCostJobPayload>} job - The job to process
|
||||
*/
|
||||
@Process(ComputeItemCostQueueJob)
|
||||
@UseCls()
|
||||
async process(job: Job<ComputeItemCostJobPayload>) {
|
||||
const { itemId, startingDate, organizationId, userId } = job.data;
|
||||
@@ -68,6 +63,12 @@ export class ComputeItemCostProcessor extends WorkerHost {
|
||||
} catch (error) {
|
||||
console.error(`[error] Error computing item cost for item ${itemId}:`, error);
|
||||
console.error('Error stack:', error instanceof Error ? error.stack : 'No stack trace');
|
||||
// Reset cost_compute_running when job fails so it does not stay true indefinitely
|
||||
try {
|
||||
await this.inventoryComputeCostService.markItemsCostComputeRunning(false);
|
||||
} catch (markError) {
|
||||
console.error('[error] Failed to mark cost compute as not running:', markError);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import { Process } from '@nestjs/bull';
|
||||
import {
|
||||
WriteInventoryTransactionsGLEntriesQueue,
|
||||
WriteInventoryTransactionsGLEntriesQueueJob,
|
||||
} from '../types/InventoryCost.types';
|
||||
import { WriteInventoryTransactionsGLEntriesQueue } from '../types/InventoryCost.types';
|
||||
import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import { Scope } from '@nestjs/common';
|
||||
|
||||
@@ -15,6 +11,5 @@ export class WriteInventoryTransactionsGLEntriesProcessor extends WorkerHost {
|
||||
super();
|
||||
}
|
||||
|
||||
@Process(WriteInventoryTransactionsGLEntriesQueueJob)
|
||||
async process() {}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
BulkDeleteItemsDto,
|
||||
ValidateBulkDeleteItemsResponseDto,
|
||||
} from './dtos/BulkDeleteItems.dto';
|
||||
import { ItemApiErrorResponseDto } from './dtos/ItemErrorResponse.dto';
|
||||
|
||||
@Controller('/items')
|
||||
@ApiTags('Items')
|
||||
@@ -45,6 +46,7 @@ import {
|
||||
@ApiExtraModels(ItemEstimatesResponseDto)
|
||||
@ApiExtraModels(ItemReceiptsResponseDto)
|
||||
@ApiExtraModels(ValidateBulkDeleteItemsResponseDto)
|
||||
@ApiExtraModels(ItemApiErrorResponseDto)
|
||||
@ApiCommonHeaders()
|
||||
export class ItemsController extends TenantController {
|
||||
constructor(private readonly itemsApplication: ItemsApplicationService) {
|
||||
@@ -147,6 +149,13 @@ export class ItemsController extends TenantController {
|
||||
status: 200,
|
||||
description: 'The item has been successfully updated.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description: 'Validation error. Possible error types: ITEM_NAME_EXISTS, INVENTORY_ACCOUNT_CANNOT_MODIFIED, TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS, etc.',
|
||||
schema: {
|
||||
$ref: getSchemaPath(ItemApiErrorResponseDto),
|
||||
},
|
||||
})
|
||||
@ApiResponse({ status: 404, description: 'The item not found.' })
|
||||
@ApiParam({
|
||||
name: 'id',
|
||||
@@ -204,6 +213,13 @@ export class ItemsController extends TenantController {
|
||||
status: 200,
|
||||
description: 'The item has been successfully created.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description: 'Validation error. Possible error types: ITEM_NAME_EXISTS, ITEM_CATEOGRY_NOT_FOUND, COST_ACCOUNT_NOT_COGS, SELL_ACCOUNT_NOT_INCOME, INVENTORY_ACCOUNT_NOT_INVENTORY, INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM, COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM, etc.',
|
||||
schema: {
|
||||
$ref: getSchemaPath(ItemApiErrorResponseDto),
|
||||
},
|
||||
})
|
||||
// @UsePipes(new ZodValidationPipe(createItemSchema))
|
||||
async createItem(
|
||||
@Body() createItemDto: CreateItemDto,
|
||||
@@ -219,6 +235,13 @@ export class ItemsController extends TenantController {
|
||||
status: 200,
|
||||
description: 'The item has been successfully deleted.',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: 400,
|
||||
description: 'Cannot delete item. Possible error types: ITEM_HAS_ASSOCIATED_TRANSACTINS, ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT, etc.',
|
||||
schema: {
|
||||
$ref: getSchemaPath(ItemApiErrorResponseDto),
|
||||
},
|
||||
})
|
||||
@ApiResponse({ status: 404, description: 'The item not found.' })
|
||||
@ApiParam({
|
||||
name: 'id',
|
||||
|
||||
112
packages/server/src/modules/Items/dtos/ItemErrorResponse.dto.ts
Normal file
112
packages/server/src/modules/Items/dtos/ItemErrorResponse.dto.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
/**
|
||||
* Item API Error Types
|
||||
* These error types are returned when item operations fail validation
|
||||
*/
|
||||
export enum ItemErrorType {
|
||||
/** Item name already exists in the system */
|
||||
ItemNameExists = 'ITEM_NAME_EXISTS',
|
||||
|
||||
/** Item category was not found */
|
||||
ItemCategoryNotFound = 'ITEM_CATEOGRY_NOT_FOUND',
|
||||
|
||||
/** Cost account is not a Cost of Goods Sold account */
|
||||
CostAccountNotCogs = 'COST_ACCOUNT_NOT_COGS',
|
||||
|
||||
/** Cost account was not found */
|
||||
CostAccountNotFound = 'COST_ACCOUNT_NOT_FOUMD',
|
||||
|
||||
/** Sell account was not found */
|
||||
SellAccountNotFound = 'SELL_ACCOUNT_NOT_FOUND',
|
||||
|
||||
/** Sell account is not an income account */
|
||||
SellAccountNotIncome = 'SELL_ACCOUNT_NOT_INCOME',
|
||||
|
||||
/** Inventory account was not found */
|
||||
InventoryAccountNotFound = 'INVENTORY_ACCOUNT_NOT_FOUND',
|
||||
|
||||
/** Account is not an inventory type account */
|
||||
InventoryAccountNotInventory = 'INVENTORY_ACCOUNT_NOT_INVENTORY',
|
||||
|
||||
/** Multiple items have associated transactions */
|
||||
ItemsHaveAssociatedTransactions = 'ITEMS_HAVE_ASSOCIATED_TRANSACTIONS',
|
||||
|
||||
/** Item has associated transactions (singular) */
|
||||
ItemHasAssociatedTransactions = 'ITEM_HAS_ASSOCIATED_TRANSACTINS',
|
||||
|
||||
/** Item has associated inventory adjustments */
|
||||
ItemHasAssociatedInventoryAdjustment = 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT',
|
||||
|
||||
/** Cannot change item type to inventory */
|
||||
ItemCannotChangeInventoryType = 'ITEM_CANNOT_CHANGE_INVENTORY_TYPE',
|
||||
|
||||
/** Cannot change type when item has transactions */
|
||||
TypeCannotChangeWithItemHasTransactions = 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
|
||||
|
||||
/** Inventory account cannot be modified */
|
||||
InventoryAccountCannotModified = 'INVENTORY_ACCOUNT_CANNOT_MODIFIED',
|
||||
|
||||
/** Purchase tax rate was not found */
|
||||
PurchaseTaxRateNotFound = 'PURCHASE_TAX_RATE_NOT_FOUND',
|
||||
|
||||
/** Sell tax rate was not found */
|
||||
SellTaxRateNotFound = 'SELL_TAX_RATE_NOT_FOUND',
|
||||
|
||||
/** Income account is required for sellable items */
|
||||
IncomeAccountRequiredWithSellableItem = 'INCOME_ACCOUNT_REQUIRED_WITH_SELLABLE_ITEM',
|
||||
|
||||
/** Cost account is required for purchasable items */
|
||||
CostAccountRequiredWithPurchasableItem = 'COST_ACCOUNT_REQUIRED_WITH_PURCHASABLE_ITEM',
|
||||
|
||||
/** Item not found */
|
||||
NotFound = 'NOT_FOUND',
|
||||
|
||||
/** Items not found */
|
||||
ItemsNotFound = 'ITEMS_NOT_FOUND',
|
||||
}
|
||||
|
||||
/**
|
||||
* Item API Error Response
|
||||
* Returned when an item operation fails
|
||||
*/
|
||||
export class ItemErrorResponseDto {
|
||||
@ApiProperty({
|
||||
description: 'HTTP status code',
|
||||
example: 400,
|
||||
})
|
||||
statusCode: number;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Error type identifier',
|
||||
enum: ItemErrorType,
|
||||
example: ItemErrorType.ItemNameExists,
|
||||
})
|
||||
type: ItemErrorType;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Human-readable error message',
|
||||
example: 'The item name is already exist.',
|
||||
required: false,
|
||||
nullable: true,
|
||||
})
|
||||
message: string | null;
|
||||
|
||||
@ApiProperty({
|
||||
description: 'Additional error payload data',
|
||||
required: false,
|
||||
nullable: true,
|
||||
})
|
||||
payload: any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Item API Error Response Wrapper
|
||||
*/
|
||||
export class ItemApiErrorResponseDto {
|
||||
@ApiProperty({
|
||||
description: 'Array of error details',
|
||||
type: [ItemErrorResponseDto],
|
||||
})
|
||||
errors: ItemErrorResponseDto[];
|
||||
}
|
||||
@@ -35,7 +35,7 @@ export const transformLedgerEntryToTransaction = (
|
||||
itemId: entry.itemId,
|
||||
projectId: entry.projectId,
|
||||
|
||||
// costable: entry.costable,
|
||||
costable: entry.costable,
|
||||
|
||||
taxRateId: entry.taxRateId,
|
||||
taxRate: entry.taxRate,
|
||||
|
||||
@@ -3,6 +3,8 @@ import { GetCurrentOrganizationService } from './queries/GetCurrentOrganization.
|
||||
import { BuildOrganizationService } from './commands/BuildOrganization.service';
|
||||
import { UpdateOrganizationService } from './commands/UpdateOrganization.service';
|
||||
import { OrganizationController } from './Organization.controller';
|
||||
import { BullBoardModule } from '@bull-board/nestjs';
|
||||
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter';
|
||||
import { BullModule } from '@nestjs/bullmq';
|
||||
import { OrganizationBuildQueue } from './Organization.types';
|
||||
import { OrganizationBuildProcessor } from './processors/OrganizationBuild.processor';
|
||||
@@ -25,10 +27,14 @@ import { GetBuildOrganizationBuildJob } from './commands/GetBuildOrganizationJob
|
||||
OrganizationBaseCurrencyLocking,
|
||||
SyncSystemUserToTenantService,
|
||||
SyncSystemUserToTenantSubscriber,
|
||||
GetBuildOrganizationBuildJob
|
||||
GetBuildOrganizationBuildJob,
|
||||
],
|
||||
imports: [
|
||||
BullModule.registerQueue({ name: OrganizationBuildQueue }),
|
||||
BullBoardModule.forFeature({
|
||||
name: OrganizationBuildQueue,
|
||||
adapter: BullMQAdapter,
|
||||
}),
|
||||
TenantDBManagerModule,
|
||||
],
|
||||
controllers: [OrganizationController],
|
||||
|
||||
@@ -2,10 +2,8 @@ import { Processor, WorkerHost } from '@nestjs/bullmq';
|
||||
import { Scope } from '@nestjs/common';
|
||||
import { Job } from 'bullmq';
|
||||
import { ClsService, UseCls } from 'nestjs-cls';
|
||||
import { Process } from '@nestjs/bull';
|
||||
import {
|
||||
OrganizationBuildQueue,
|
||||
OrganizationBuildQueueJob,
|
||||
OrganizationBuildQueueJobPayload,
|
||||
} from '../Organization.types';
|
||||
import { BuildOrganizationService } from '../commands/BuildOrganization.service';
|
||||
@@ -22,7 +20,6 @@ export class OrganizationBuildProcessor extends WorkerHost {
|
||||
super();
|
||||
}
|
||||
|
||||
@Process(OrganizationBuildQueueJob)
|
||||
@UseCls()
|
||||
async process(job: Job<OrganizationBuildQueueJobPayload>) {
|
||||
console.log('Processing organization build job:', job.id);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { BullBoardModule } from '@bull-board/nestjs';
|
||||
import { BullAdapter } from '@bull-board/api/bullAdapter';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
import { PaymentReceivesController } from './PaymentsReceived.controller';
|
||||
import { PaymentReceivesApplication } from './PaymentReceived.application';
|
||||
@@ -95,6 +97,10 @@ import { ValidateBulkDeletePaymentReceivedService } from './ValidateBulkDeletePa
|
||||
DynamicListModule,
|
||||
MailModule,
|
||||
BullModule.registerQueue({ name: SEND_PAYMENT_RECEIVED_MAIL_QUEUE }),
|
||||
BullBoardModule.forFeature({
|
||||
name: SEND_PAYMENT_RECEIVED_MAIL_QUEUE,
|
||||
adapter: BullAdapter,
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class PaymentsReceivedModule {}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { BullBoardModule } from '@bull-board/nestjs';
|
||||
import { BullAdapter } from '@bull-board/api/bullAdapter';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
import { TenancyContext } from '../Tenancy/TenancyContext.service';
|
||||
import { TenancyDatabaseModule } from '../Tenancy/TenancyDB/TenancyDB.module';
|
||||
@@ -54,6 +56,10 @@ import { SendSaleEstimateMailProcess } from './processes/SendSaleEstimateMail.pr
|
||||
TemplateInjectableModule,
|
||||
PdfTemplatesModule,
|
||||
BullModule.registerQueue({ name: SendSaleEstimateMailQueue }),
|
||||
BullBoardModule.forFeature({
|
||||
name: SendSaleEstimateMailQueue,
|
||||
adapter: BullAdapter,
|
||||
}),
|
||||
],
|
||||
controllers: [SaleEstimatesController],
|
||||
providers: [
|
||||
@@ -99,4 +105,4 @@ import { SendSaleEstimateMailProcess } from './processes/SendSaleEstimateMail.pr
|
||||
GetSaleEstimateMailTemplateService,
|
||||
],
|
||||
})
|
||||
export class SaleEstimatesModule { }
|
||||
export class SaleEstimatesModule {}
|
||||
|
||||
@@ -45,6 +45,8 @@ import { SendSaleInvoiceMailCommon } from './commands/SendInvoiceInvoiceMailComm
|
||||
import { DynamicListModule } from '../DynamicListing/DynamicList.module';
|
||||
import { MailNotificationModule } from '../MailNotification/MailNotification.module';
|
||||
import { SendSaleInvoiceMailProcessor } from './processors/SendSaleInvoiceMail.processor';
|
||||
import { BullBoardModule } from '@bull-board/nestjs';
|
||||
import { BullAdapter } from '@bull-board/api/bullAdapter';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
import { SendSaleInvoiceQueue } from './constants';
|
||||
import { InvoicePaymentIntegrationSubscriber } from './subscribers/InvoicePaymentIntegrationSubscriber';
|
||||
@@ -81,6 +83,10 @@ import { ValidateBulkDeleteSaleInvoicesService } from './ValidateBulkDeleteSaleI
|
||||
forwardRef(() => PaymentLinksModule),
|
||||
DynamicListModule,
|
||||
BullModule.registerQueue({ name: SendSaleInvoiceQueue }),
|
||||
BullBoardModule.forFeature({
|
||||
name: SendSaleInvoiceQueue,
|
||||
adapter: BullAdapter,
|
||||
}),
|
||||
],
|
||||
controllers: [SaleInvoicesController],
|
||||
providers: [
|
||||
@@ -139,4 +145,4 @@ import { ValidateBulkDeleteSaleInvoicesService } from './ValidateBulkDeleteSaleI
|
||||
SaleInvoicesImportable,
|
||||
],
|
||||
})
|
||||
export class SaleInvoicesModule { }
|
||||
export class SaleInvoicesModule {}
|
||||
|
||||
@@ -114,7 +114,6 @@ export class SaleInvoicesCost {
|
||||
*/
|
||||
scheduleWriteJournalEntries(startingDate?: Date) {
|
||||
// const agenda = Container.get('agenda');
|
||||
|
||||
// return agenda.schedule('in 3 seconds', 'rewrite-invoices-journal-entries', {
|
||||
// startingDate,
|
||||
// tenantId,
|
||||
@@ -123,12 +122,11 @@ export class SaleInvoicesCost {
|
||||
|
||||
/**
|
||||
* Writes cost GL entries from the inventory cost lots.
|
||||
* @param {number} tenantId -
|
||||
* @param {Date} startingDate -
|
||||
* @param {Date} startingDate - Starting date.
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
public writeCostLotsGLEntries = (startingDate: Date) => {
|
||||
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
public writeCostLotsGLEntries = async (startingDate: Date) => {
|
||||
await this.uow.withTransaction(async (trx: Knex.Transaction) => {
|
||||
// Triggers event `onInventoryCostLotsGLEntriesBeforeWrite`.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.inventory.onCostLotsGLEntriesBeforeWrite,
|
||||
@@ -146,5 +144,10 @@ export class SaleInvoicesCost {
|
||||
} as IInventoryCostLotsGLEntriesWriteEvent,
|
||||
);
|
||||
});
|
||||
// Signal that cost entries have been written so cost_compute_running can be set to false.
|
||||
await this.eventPublisher.emitAsync(
|
||||
events.inventory.onInventoryCostEntriesWritten,
|
||||
{},
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
import * as R from 'ramda';
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { TenantModelProxy } from '../System/models/TenantBaseModel';
|
||||
import { InventoryCostLotTracker } from '../InventoryCost/models/InventoryCostLotTracker';
|
||||
import { LedgerStorageService } from '../Ledger/LedgerStorage.service';
|
||||
import { groupInventoryTransactionsByTypeId } from '../InventoryCost/utils';
|
||||
import { Ledger } from '../Ledger/Ledger';
|
||||
import { AccountNormal } from '@/interfaces/Account';
|
||||
import { ILedgerEntry } from '../Ledger/types/Ledger.types';
|
||||
import { increment } from '@/utils/increment';
|
||||
|
||||
@Injectable()
|
||||
export class SaleReceiptCostGLEntries {
|
||||
constructor(
|
||||
private readonly ledgerStorage: LedgerStorageService,
|
||||
|
||||
@Inject(InventoryCostLotTracker.name)
|
||||
private readonly inventoryCostLotTracker: TenantModelProxy<
|
||||
typeof InventoryCostLotTracker
|
||||
>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Writes journal entries from sales receipts.
|
||||
* @param {Date} startingDate - Starting date.
|
||||
* @param {Knex.Transaction} trx - Transaction.
|
||||
*/
|
||||
public writeInventoryCostJournalEntries = async (
|
||||
startingDate: Date,
|
||||
trx?: Knex.Transaction,
|
||||
): Promise<void> => {
|
||||
const inventoryCostLotTrans = await this.inventoryCostLotTracker()
|
||||
.query()
|
||||
.where('direction', 'OUT')
|
||||
.where('transaction_type', 'SaleReceipt')
|
||||
.where('cost', '>', 0)
|
||||
.modify('filterDateRange', startingDate)
|
||||
.orderBy('date', 'ASC')
|
||||
.withGraphFetched('receipt')
|
||||
.withGraphFetched('item')
|
||||
.withGraphFetched('itemEntry');
|
||||
|
||||
const ledger = this.getInventoryCostLotsLedger(inventoryCostLotTrans);
|
||||
|
||||
await this.ledgerStorage.commit(ledger, trx);
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the inventory cost lots ledger.
|
||||
*/
|
||||
private getInventoryCostLotsLedger = (
|
||||
inventoryCostLots: InventoryCostLotTracker[],
|
||||
) => {
|
||||
const inventoryTransactions =
|
||||
groupInventoryTransactionsByTypeId(inventoryCostLots);
|
||||
|
||||
const entries = inventoryTransactions
|
||||
.map(this.getSaleReceiptCostGLEntries)
|
||||
.flat();
|
||||
return new Ledger(entries);
|
||||
};
|
||||
|
||||
/**
|
||||
* Builds the common GL entry fields for a sale receipt cost.
|
||||
*/
|
||||
private getReceiptCostGLCommonEntry = (
|
||||
inventoryCostLot: InventoryCostLotTracker,
|
||||
) => {
|
||||
return {
|
||||
currencyCode: inventoryCostLot.receipt.currencyCode,
|
||||
exchangeRate: inventoryCostLot.receipt.exchangeRate,
|
||||
|
||||
transactionType: inventoryCostLot.transactionType,
|
||||
transactionId: inventoryCostLot.transactionId,
|
||||
|
||||
transactionNumber: inventoryCostLot.receipt.receiptNumber,
|
||||
referenceNumber: inventoryCostLot.receipt.referenceNo,
|
||||
|
||||
date: inventoryCostLot.date,
|
||||
indexGroup: 20,
|
||||
costable: true,
|
||||
createdAt: inventoryCostLot.createdAt,
|
||||
|
||||
debit: 0,
|
||||
credit: 0,
|
||||
|
||||
branchId: inventoryCostLot.receipt.branchId,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the inventory cost GL entry for a single lot.
|
||||
*/
|
||||
private getInventoryCostGLEntry = R.curry(
|
||||
(
|
||||
getIndexIncrement: () => number,
|
||||
inventoryCostLot: InventoryCostLotTracker,
|
||||
): ILedgerEntry[] => {
|
||||
const commonEntry = this.getReceiptCostGLCommonEntry(inventoryCostLot);
|
||||
const costAccountId =
|
||||
inventoryCostLot.costAccountId || inventoryCostLot.item.costAccountId;
|
||||
|
||||
const description = inventoryCostLot.itemEntry?.description || null;
|
||||
|
||||
const costEntry = {
|
||||
...commonEntry,
|
||||
debit: inventoryCostLot.cost,
|
||||
accountId: costAccountId,
|
||||
accountNormal: AccountNormal.DEBIT,
|
||||
itemId: inventoryCostLot.itemId,
|
||||
note: description,
|
||||
index: getIndexIncrement(),
|
||||
};
|
||||
|
||||
const inventoryEntry = {
|
||||
...commonEntry,
|
||||
credit: inventoryCostLot.cost,
|
||||
accountId: inventoryCostLot.item.inventoryAccountId,
|
||||
accountNormal: AccountNormal.DEBIT,
|
||||
itemId: inventoryCostLot.itemId,
|
||||
note: description,
|
||||
index: getIndexIncrement(),
|
||||
};
|
||||
return [costEntry, inventoryEntry];
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Builds GL entries for a group of sale receipt cost lots.
|
||||
* - Cost of goods sold -> Debit
|
||||
* - Inventory assets -> Credit
|
||||
*/
|
||||
public getSaleReceiptCostGLEntries = (
|
||||
inventoryCostLots: InventoryCostLotTracker[],
|
||||
): ILedgerEntry[] => {
|
||||
const getIndexIncrement = increment(0);
|
||||
const getInventoryLotEntry =
|
||||
this.getInventoryCostGLEntry(getIndexIncrement);
|
||||
|
||||
return inventoryCostLots.map((t) => getInventoryLotEntry(t)).flat();
|
||||
};
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { BullBoardModule } from '@bull-board/nestjs';
|
||||
import { BullAdapter } from '@bull-board/api/bullAdapter';
|
||||
import { BullModule } from '@nestjs/bull';
|
||||
import { SaleReceiptApplication } from './SaleReceiptApplication.service';
|
||||
import { CreateSaleReceipt } from './commands/CreateSaleReceipt.service';
|
||||
@@ -40,6 +42,8 @@ import { SaleReceiptsImportable } from './commands/SaleReceiptsImportable';
|
||||
import { GetSaleReceiptMailStateService } from './queries/GetSaleReceiptMailState.service';
|
||||
import { GetSaleReceiptMailTemplateService } from './queries/GetSaleReceiptMailTemplate.service';
|
||||
import { SaleReceiptAutoIncrementSubscriber } from './subscribers/SaleReceiptAutoIncrementSubscriber';
|
||||
import { SaleReceiptCostGLEntriesSubscriber } from './subscribers/SaleReceiptCostGLEntriesSubscriber';
|
||||
import { SaleReceiptCostGLEntries } from './SaleReceiptCostGLEntries';
|
||||
import { BulkDeleteSaleReceiptsService } from './BulkDeleteSaleReceipts.service';
|
||||
import { ValidateBulkDeleteSaleReceiptsService } from './ValidateBulkDeleteSaleReceipts.service';
|
||||
|
||||
@@ -60,6 +64,10 @@ import { ValidateBulkDeleteSaleReceiptsService } from './ValidateBulkDeleteSaleR
|
||||
MailModule,
|
||||
MailNotificationModule,
|
||||
BullModule.registerQueue({ name: SendSaleReceiptMailQueue }),
|
||||
BullBoardModule.forFeature({
|
||||
name: SendSaleReceiptMailQueue,
|
||||
adapter: BullAdapter,
|
||||
}),
|
||||
],
|
||||
providers: [
|
||||
TenancyContext,
|
||||
@@ -87,8 +95,10 @@ import { ValidateBulkDeleteSaleReceiptsService } from './ValidateBulkDeleteSaleR
|
||||
GetSaleReceiptMailStateService,
|
||||
GetSaleReceiptMailTemplateService,
|
||||
SaleReceiptAutoIncrementSubscriber,
|
||||
SaleReceiptCostGLEntries,
|
||||
SaleReceiptCostGLEntriesSubscriber,
|
||||
BulkDeleteSaleReceiptsService,
|
||||
ValidateBulkDeleteSaleReceiptsService,
|
||||
],
|
||||
})
|
||||
export class SaleReceiptsModule { }
|
||||
export class SaleReceiptsModule {}
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
// import { Service, Inject } from 'typedi';
|
||||
// import * as R from 'ramda';
|
||||
// import { Knex } from 'knex';
|
||||
// import { AccountNormal, IInventoryLotCost, ILedgerEntry } from '@/interfaces';
|
||||
// import { increment } from 'utils';
|
||||
// import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
// import Ledger from '@/services/Accounting/Ledger';
|
||||
// import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
||||
// import { groupInventoryTransactionsByTypeId } from '../../Inventory/utils';
|
||||
|
||||
// @Service()
|
||||
// export class SaleReceiptCostGLEntries {
|
||||
// @Inject()
|
||||
// private tenancy: HasTenancyService;
|
||||
|
||||
// @Inject()
|
||||
// private ledgerStorage: LedgerStorageService;
|
||||
|
||||
// /**
|
||||
// * Writes journal entries from sales invoices.
|
||||
// * @param {number} tenantId - The tenant id.
|
||||
// * @param {Date} startingDate - Starting date.
|
||||
// * @param {boolean} override
|
||||
// */
|
||||
// public writeInventoryCostJournalEntries = async (
|
||||
// tenantId: number,
|
||||
// startingDate: Date,
|
||||
// trx?: Knex.Transaction
|
||||
// ): Promise<void> => {
|
||||
// const { InventoryCostLotTracker } = this.tenancy.models(tenantId);
|
||||
|
||||
// const inventoryCostLotTrans = await InventoryCostLotTracker.query()
|
||||
// .where('direction', 'OUT')
|
||||
// .where('transaction_type', 'SaleReceipt')
|
||||
// .where('cost', '>', 0)
|
||||
// .modify('filterDateRange', startingDate)
|
||||
// .orderBy('date', 'ASC')
|
||||
// .withGraphFetched('receipt')
|
||||
// .withGraphFetched('item');
|
||||
|
||||
// const ledger = this.getInventoryCostLotsLedger(inventoryCostLotTrans);
|
||||
|
||||
// // Commit the ledger to the storage.
|
||||
// await this.ledgerStorage.commit(tenantId, ledger, trx);
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Retrieves the inventory cost lots ledger.
|
||||
// * @param {} inventoryCostLots
|
||||
// * @returns {Ledger}
|
||||
// */
|
||||
// private getInventoryCostLotsLedger = (
|
||||
// inventoryCostLots: IInventoryLotCost[]
|
||||
// ) => {
|
||||
// // Groups the inventory cost lots transactions.
|
||||
// const inventoryTransactions =
|
||||
// groupInventoryTransactionsByTypeId(inventoryCostLots);
|
||||
|
||||
// //
|
||||
// const entries = inventoryTransactions
|
||||
// .map(this.getSaleInvoiceCostGLEntries)
|
||||
// .flat();
|
||||
|
||||
// return new Ledger(entries);
|
||||
// };
|
||||
|
||||
// /**
|
||||
// *
|
||||
// * @param {IInventoryLotCost} inventoryCostLot
|
||||
// * @returns {}
|
||||
// */
|
||||
// private getInvoiceCostGLCommonEntry = (
|
||||
// inventoryCostLot: IInventoryLotCost
|
||||
// ) => {
|
||||
// return {
|
||||
// currencyCode: inventoryCostLot.receipt.currencyCode,
|
||||
// exchangeRate: inventoryCostLot.receipt.exchangeRate,
|
||||
|
||||
// transactionType: inventoryCostLot.transactionType,
|
||||
// transactionId: inventoryCostLot.transactionId,
|
||||
|
||||
// date: inventoryCostLot.date,
|
||||
// indexGroup: 20,
|
||||
// costable: true,
|
||||
// createdAt: inventoryCostLot.createdAt,
|
||||
|
||||
// debit: 0,
|
||||
// credit: 0,
|
||||
|
||||
// branchId: inventoryCostLot.receipt.branchId,
|
||||
// };
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Retrieves the inventory cost GL entry.
|
||||
// * @param {IInventoryLotCost} inventoryLotCost
|
||||
// * @returns {ILedgerEntry[]}
|
||||
// */
|
||||
// private getInventoryCostGLEntry = R.curry(
|
||||
// (
|
||||
// getIndexIncrement,
|
||||
// inventoryCostLot: IInventoryLotCost
|
||||
// ): ILedgerEntry[] => {
|
||||
// const commonEntry = this.getInvoiceCostGLCommonEntry(inventoryCostLot);
|
||||
// const costAccountId =
|
||||
// inventoryCostLot.costAccountId || inventoryCostLot.item.costAccountId;
|
||||
|
||||
// // XXX Debit - Cost account.
|
||||
// const costEntry = {
|
||||
// ...commonEntry,
|
||||
// debit: inventoryCostLot.cost,
|
||||
// accountId: costAccountId,
|
||||
// accountNormal: AccountNormal.DEBIT,
|
||||
// itemId: inventoryCostLot.itemId,
|
||||
// index: getIndexIncrement(),
|
||||
// };
|
||||
// // XXX Credit - Inventory account.
|
||||
// const inventoryEntry = {
|
||||
// ...commonEntry,
|
||||
// credit: inventoryCostLot.cost,
|
||||
// accountId: inventoryCostLot.item.inventoryAccountId,
|
||||
// accountNormal: AccountNormal.DEBIT,
|
||||
// itemId: inventoryCostLot.itemId,
|
||||
// index: getIndexIncrement(),
|
||||
// };
|
||||
// return [costEntry, inventoryEntry];
|
||||
// }
|
||||
// );
|
||||
|
||||
// /**
|
||||
// * Writes journal entries for given sale invoice.
|
||||
// * -------
|
||||
// * - Cost of goods sold -> Debit -> YYYY
|
||||
// * - Inventory assets -> Credit -> YYYY
|
||||
// * --------
|
||||
// * @param {ISaleInvoice} saleInvoice
|
||||
// * @param {JournalPoster} journal
|
||||
// */
|
||||
// public getSaleInvoiceCostGLEntries = (
|
||||
// inventoryCostLots: IInventoryLotCost[]
|
||||
// ): ILedgerEntry[] => {
|
||||
// const getIndexIncrement = increment(0);
|
||||
// const getInventoryLotEntry =
|
||||
// this.getInventoryCostGLEntry(getIndexIncrement);
|
||||
|
||||
// return inventoryCostLots.map(getInventoryLotEntry).flat();
|
||||
// };
|
||||
// }
|
||||
@@ -1,36 +1,26 @@
|
||||
// import { Inject, Service } from 'typedi';
|
||||
// import events from '@/subscribers/events';
|
||||
// import { IInventoryCostLotsGLEntriesWriteEvent } from '@/interfaces';
|
||||
// import { SaleReceiptCostGLEntries } from '../SaleReceiptCostGLEntries';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
import { IInventoryCostLotsGLEntriesWriteEvent } from '@/modules/InventoryCost/types/InventoryCost.types';
|
||||
import { SaleReceiptCostGLEntries } from '../SaleReceiptCostGLEntries';
|
||||
|
||||
// @Service()
|
||||
// export class SaleReceiptCostGLEntriesSubscriber {
|
||||
// @Inject()
|
||||
// private saleReceiptCostEntries: SaleReceiptCostGLEntries;
|
||||
@Injectable()
|
||||
export class SaleReceiptCostGLEntriesSubscriber {
|
||||
constructor(
|
||||
private readonly saleReceiptCostEntries: SaleReceiptCostGLEntries,
|
||||
) {}
|
||||
|
||||
// /**
|
||||
// * Attaches events.
|
||||
// */
|
||||
// public attach(bus) {
|
||||
// bus.subscribe(
|
||||
// events.inventory.onCostLotsGLEntriesWrite,
|
||||
// this.writeJournalEntriesOnceWriteoffCreate
|
||||
// );
|
||||
// }
|
||||
|
||||
// /**
|
||||
// * Writes the receipts cost GL entries once the inventory cost lots be written.
|
||||
// * @param {IInventoryCostLotsGLEntriesWriteEvent}
|
||||
// */
|
||||
// private writeJournalEntriesOnceWriteoffCreate = async ({
|
||||
// trx,
|
||||
// startingDate,
|
||||
// tenantId,
|
||||
// }: IInventoryCostLotsGLEntriesWriteEvent) => {
|
||||
// await this.saleReceiptCostEntries.writeInventoryCostJournalEntries(
|
||||
// tenantId,
|
||||
// startingDate,
|
||||
// trx
|
||||
// );
|
||||
// };
|
||||
// }
|
||||
/**
|
||||
* Writes the receipts cost GL entries once the inventory cost lots are written.
|
||||
*/
|
||||
@OnEvent(events.inventory.onCostLotsGLEntriesWrite)
|
||||
async writeReceiptsCostEntriesOnCostLotsWritten({
|
||||
trx,
|
||||
startingDate,
|
||||
}: IInventoryCostLotsGLEntriesWriteEvent) {
|
||||
await this.saleReceiptCostEntries.writeInventoryCostJournalEntries(
|
||||
startingDate,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ const providers = [
|
||||
},
|
||||
migrations: {
|
||||
directory: configService.get('systemDatabase.migrationDir'),
|
||||
loadExtensions: ['.js'],
|
||||
},
|
||||
seeds: {
|
||||
directory: configService.get('systemDatabase.seedsDir'),
|
||||
|
||||
@@ -33,6 +33,7 @@ export const TenancyDatabaseProxyProvider = ClsModule.forFeatureAsync({
|
||||
},
|
||||
migrations: {
|
||||
directory: configService.get('tenantDatabase.migrationsDir'),
|
||||
loadExtensions: ['.js'],
|
||||
},
|
||||
seeds: {
|
||||
directory: configService.get('tenantDatabase.seedsDir'),
|
||||
|
||||
@@ -1,115 +1,116 @@
|
||||
// import { Service } from 'typedi';
|
||||
// import { IVendor, AccountNormal, ILedgerEntry } from '@/interfaces';
|
||||
// import Ledger from '@/services/Accounting/Ledger';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { AccountNormal } from '@/interfaces/Account';
|
||||
import { ILedgerEntry } from '@/modules/Ledger/types/Ledger.types';
|
||||
import { Ledger } from '@/modules/Ledger/Ledger';
|
||||
import { Vendor } from './models/Vendor';
|
||||
|
||||
// @Service()
|
||||
// export class VendorGLEntries {
|
||||
// /**
|
||||
// * Retrieves the opening balance GL common entry.
|
||||
// * @param {IVendor} vendor -
|
||||
// */
|
||||
// private getOpeningBalanceGLCommonEntry = (vendor: IVendor) => {
|
||||
// return {
|
||||
// exchangeRate: vendor.openingBalanceExchangeRate,
|
||||
// currencyCode: vendor.currencyCode,
|
||||
@Injectable()
|
||||
export class VendorGLEntries {
|
||||
/**
|
||||
* Retrieves the opening balance GL common entry.
|
||||
* @param {Vendor} vendor -
|
||||
*/
|
||||
private getOpeningBalanceGLCommonEntry = (vendor: Vendor) => {
|
||||
return {
|
||||
exchangeRate: vendor.openingBalanceExchangeRate,
|
||||
currencyCode: vendor.currencyCode,
|
||||
|
||||
// transactionType: 'VendorOpeningBalance',
|
||||
// transactionId: vendor.id,
|
||||
transactionType: 'VendorOpeningBalance',
|
||||
transactionId: vendor.id,
|
||||
|
||||
// date: vendor.openingBalanceAt,
|
||||
// userId: vendor.userId,
|
||||
// contactId: vendor.id,
|
||||
date: vendor.openingBalanceAt,
|
||||
contactId: vendor.id,
|
||||
|
||||
// credit: 0,
|
||||
// debit: 0,
|
||||
credit: 0,
|
||||
debit: 0,
|
||||
|
||||
// branchId: vendor.openingBalanceBranchId,
|
||||
// };
|
||||
// };
|
||||
branchId: vendor.openingBalanceBranchId,
|
||||
};
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Retrieves the opening balance GL debit entry.
|
||||
// * @param {number} costAccountId -
|
||||
// * @param {IVendor} vendor
|
||||
// * @returns {ILedgerEntry}
|
||||
// */
|
||||
// private getOpeningBalanceGLDebitEntry = (
|
||||
// costAccountId: number,
|
||||
// vendor: IVendor
|
||||
// ): ILedgerEntry => {
|
||||
// const commonEntry = this.getOpeningBalanceGLCommonEntry(vendor);
|
||||
/**
|
||||
* Retrieves the opening balance GL debit entry.
|
||||
* @param {number} costAccountId -
|
||||
* @param {Vendor} vendor
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getOpeningBalanceGLDebitEntry = (
|
||||
costAccountId: number,
|
||||
vendor: Vendor
|
||||
): ILedgerEntry => {
|
||||
const commonEntry = this.getOpeningBalanceGLCommonEntry(vendor);
|
||||
|
||||
// return {
|
||||
// ...commonEntry,
|
||||
// accountId: costAccountId,
|
||||
// accountNormal: AccountNormal.DEBIT,
|
||||
// debit: vendor.localOpeningBalance,
|
||||
// credit: 0,
|
||||
// index: 2,
|
||||
// };
|
||||
// };
|
||||
return {
|
||||
...commonEntry,
|
||||
accountId: costAccountId,
|
||||
accountNormal: AccountNormal.DEBIT,
|
||||
debit: vendor.localOpeningBalance,
|
||||
credit: 0,
|
||||
index: 2,
|
||||
};
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Retrieves the opening balance GL credit entry.
|
||||
// * @param {number} APAccountId
|
||||
// * @param {IVendor} vendor
|
||||
// * @returns {ILedgerEntry}
|
||||
// */
|
||||
// private getOpeningBalanceGLCreditEntry = (
|
||||
// APAccountId: number,
|
||||
// vendor: IVendor
|
||||
// ): ILedgerEntry => {
|
||||
// const commonEntry = this.getOpeningBalanceGLCommonEntry(vendor);
|
||||
/**
|
||||
* Retrieves the opening balance GL credit entry.
|
||||
* @param {number} APAccountId
|
||||
* @param {Vendor} vendor
|
||||
* @returns {ILedgerEntry}
|
||||
*/
|
||||
private getOpeningBalanceGLCreditEntry = (
|
||||
APAccountId: number,
|
||||
vendor: Vendor
|
||||
): ILedgerEntry => {
|
||||
const commonEntry = this.getOpeningBalanceGLCommonEntry(vendor);
|
||||
|
||||
// return {
|
||||
// ...commonEntry,
|
||||
// accountId: APAccountId,
|
||||
// accountNormal: AccountNormal.CREDIT,
|
||||
// credit: vendor.localOpeningBalance,
|
||||
// index: 1,
|
||||
// };
|
||||
// };
|
||||
return {
|
||||
...commonEntry,
|
||||
accountId: APAccountId,
|
||||
accountNormal: AccountNormal.CREDIT,
|
||||
credit: vendor.localOpeningBalance,
|
||||
index: 1,
|
||||
};
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Retrieves the opening balance GL entries.
|
||||
// * @param {number} APAccountId
|
||||
// * @param {number} costAccountId -
|
||||
// * @param {IVendor} vendor
|
||||
// * @returns {ILedgerEntry[]}
|
||||
// */
|
||||
// public getOpeningBalanceGLEntries = (
|
||||
// APAccountId: number,
|
||||
// costAccountId: number,
|
||||
// vendor: IVendor
|
||||
// ): ILedgerEntry[] => {
|
||||
// const debitEntry = this.getOpeningBalanceGLDebitEntry(
|
||||
// costAccountId,
|
||||
// vendor
|
||||
// );
|
||||
// const creditEntry = this.getOpeningBalanceGLCreditEntry(
|
||||
// APAccountId,
|
||||
// vendor
|
||||
// );
|
||||
// return [debitEntry, creditEntry];
|
||||
// };
|
||||
/**
|
||||
* Retrieves the opening balance GL entries.
|
||||
* @param {number} APAccountId
|
||||
* @param {number} costAccountId -
|
||||
* @param {Vendor} vendor
|
||||
* @returns {ILedgerEntry[]}
|
||||
*/
|
||||
public getOpeningBalanceGLEntries = (
|
||||
APAccountId: number,
|
||||
costAccountId: number,
|
||||
vendor: Vendor
|
||||
): ILedgerEntry[] => {
|
||||
const debitEntry = this.getOpeningBalanceGLDebitEntry(
|
||||
costAccountId,
|
||||
vendor
|
||||
);
|
||||
const creditEntry = this.getOpeningBalanceGLCreditEntry(
|
||||
APAccountId,
|
||||
vendor
|
||||
);
|
||||
return [debitEntry, creditEntry];
|
||||
};
|
||||
|
||||
// /**
|
||||
// * Retrieves the opening balance ledger.
|
||||
// * @param {number} APAccountId
|
||||
// * @param {number} costAccountId -
|
||||
// * @param {IVendor} vendor
|
||||
// * @returns {Ledger}
|
||||
// */
|
||||
// public getOpeningBalanceLedger = (
|
||||
// APAccountId: number,
|
||||
// costAccountId: number,
|
||||
// vendor: IVendor
|
||||
// ) => {
|
||||
// const entries = this.getOpeningBalanceGLEntries(
|
||||
// APAccountId,
|
||||
// costAccountId,
|
||||
// vendor
|
||||
// );
|
||||
// return new Ledger(entries);
|
||||
// };
|
||||
// }
|
||||
/**
|
||||
* Retrieves the opening balance ledger.
|
||||
* @param {number} APAccountId
|
||||
* @param {number} costAccountId -
|
||||
* @param {Vendor} vendor
|
||||
* @returns {Ledger}
|
||||
*/
|
||||
public getOpeningBalanceLedger = (
|
||||
APAccountId: number,
|
||||
costAccountId: number,
|
||||
vendor: Vendor
|
||||
) => {
|
||||
const entries = this.getOpeningBalanceGLEntries(
|
||||
APAccountId,
|
||||
costAccountId,
|
||||
vendor
|
||||
);
|
||||
return new Ledger(entries);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,88 +1,86 @@
|
||||
// import { Knex } from 'knex';
|
||||
// import { Service, Inject } from 'typedi';
|
||||
// import LedgerStorageService from '@/services/Accounting/LedgerStorageService';
|
||||
// import HasTenancyService from '@/services/Tenancy/TenancyService';
|
||||
// import { VendorGLEntries } from './VendorGLEntries';
|
||||
import { Knex } from 'knex';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { LedgerStorageService } from '@/modules/Ledger/LedgerStorage.service';
|
||||
import { AccountRepository } from '@/modules/Accounts/repositories/Account.repository';
|
||||
import { VendorGLEntries } from './VendorGLEntries';
|
||||
import { Vendor } from './models/Vendor';
|
||||
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
|
||||
|
||||
// @Service()
|
||||
// export class VendorGLEntriesStorage {
|
||||
// @Inject()
|
||||
// private tenancy: HasTenancyService;
|
||||
@Injectable()
|
||||
export class VendorGLEntriesStorage {
|
||||
constructor(
|
||||
private readonly ledgerStorage: LedgerStorageService,
|
||||
private readonly accountRepository: AccountRepository,
|
||||
private readonly vendorGLEntries: VendorGLEntries,
|
||||
|
||||
// @Inject()
|
||||
// private ledegrRepository: LedgerStorageService;
|
||||
@Inject(Vendor.name)
|
||||
private readonly vendorModel: TenantModelProxy<typeof Vendor>,
|
||||
) { }
|
||||
|
||||
// @Inject()
|
||||
// private vendorGLEntries: VendorGLEntries;
|
||||
/**
|
||||
* Vendor opening balance journals.
|
||||
* @param {number} vendorId
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public writeVendorOpeningBalance = async (
|
||||
vendorId: number,
|
||||
trx?: Knex.Transaction,
|
||||
) => {
|
||||
const vendor = await this.vendorModel()
|
||||
.query(trx)
|
||||
.findById(vendorId);
|
||||
|
||||
// /**
|
||||
// * Vendor opening balance journals.
|
||||
// * @param {number} tenantId
|
||||
// * @param {number} vendorId
|
||||
// * @param {Knex.Transaction} trx
|
||||
// */
|
||||
// public writeVendorOpeningBalance = async (
|
||||
// tenantId: number,
|
||||
// vendorId: number,
|
||||
// trx?: Knex.Transaction
|
||||
// ) => {
|
||||
// const { Vendor } = this.tenancy.models(tenantId);
|
||||
// const { accountRepository } = this.tenancy.repositories(tenantId);
|
||||
// Finds the expense account.
|
||||
const expenseAccount = await this.accountRepository.findOrCreateOtherExpensesAccount(
|
||||
{},
|
||||
trx,
|
||||
);
|
||||
// Find or create the A/P account.
|
||||
const APAccount =
|
||||
await this.accountRepository.findOrCreateAccountsPayable(
|
||||
vendor.currencyCode,
|
||||
{},
|
||||
trx,
|
||||
);
|
||||
// Retrieves the vendor opening balance ledger.
|
||||
const ledger = this.vendorGLEntries.getOpeningBalanceLedger(
|
||||
APAccount.id,
|
||||
expenseAccount.id,
|
||||
vendor,
|
||||
);
|
||||
// Commits the ledger entries to the storage.
|
||||
await this.ledgerStorage.commit(ledger, trx);
|
||||
};
|
||||
|
||||
// const vendor = await Vendor.query(trx).findById(vendorId);
|
||||
/**
|
||||
* Reverts the vendor opening balance GL entries.
|
||||
* @param {number} vendorId
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public revertVendorOpeningBalance = async (
|
||||
vendorId: number,
|
||||
trx?: Knex.Transaction,
|
||||
) => {
|
||||
await this.ledgerStorage.deleteByReference(
|
||||
vendorId,
|
||||
'VendorOpeningBalance',
|
||||
trx,
|
||||
);
|
||||
};
|
||||
|
||||
// // Finds the expense account.
|
||||
// const expenseAccount = await accountRepository.findOne({
|
||||
// slug: 'other-expenses',
|
||||
// });
|
||||
// // Find or create the A/P account.
|
||||
// const APAccount = await accountRepository.findOrCreateAccountsPayable(
|
||||
// vendor.currencyCode,
|
||||
// {},
|
||||
// trx
|
||||
// );
|
||||
// // Retrieves the vendor opening balance ledger.
|
||||
// const ledger = this.vendorGLEntries.getOpeningBalanceLedger(
|
||||
// APAccount.id,
|
||||
// expenseAccount.id,
|
||||
// vendor
|
||||
// );
|
||||
// // Commits the ledger entries to the storage.
|
||||
// await this.ledegrRepository.commit(tenantId, ledger, trx);
|
||||
// };
|
||||
/**
|
||||
* Writes the vendor opening balance GL entries.
|
||||
* @param {number} vendorId
|
||||
* @param {Knex.Transaction} trx
|
||||
*/
|
||||
public rewriteVendorOpeningBalance = async (
|
||||
vendorId: number,
|
||||
trx?: Knex.Transaction,
|
||||
) => {
|
||||
// Reverts the vendor opening balance entries first.
|
||||
await this.revertVendorOpeningBalance(vendorId, trx);
|
||||
|
||||
// /**
|
||||
// * Reverts the vendor opening balance GL entries.
|
||||
// * @param {number} tenantId
|
||||
// * @param {number} vendorId
|
||||
// * @param {Knex.Transaction} trx
|
||||
// */
|
||||
// public revertVendorOpeningBalance = async (
|
||||
// tenantId: number,
|
||||
// vendorId: number,
|
||||
// trx?: Knex.Transaction
|
||||
// ) => {
|
||||
// await this.ledegrRepository.deleteByReference(
|
||||
// tenantId,
|
||||
// vendorId,
|
||||
// 'VendorOpeningBalance',
|
||||
// trx
|
||||
// );
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Writes the vendor opening balance GL entries.
|
||||
// * @param {number} tenantId
|
||||
// * @param {number} vendorId
|
||||
// * @param {Knex.Transaction} trx
|
||||
// */
|
||||
// public rewriteVendorOpeningBalance = async (
|
||||
// tenantId: number,
|
||||
// vendorId: number,
|
||||
// trx?: Knex.Transaction
|
||||
// ) => {
|
||||
// await this.writeVendorOpeningBalance(tenantId, vendorId, trx);
|
||||
|
||||
// await this.revertVendorOpeningBalance(tenantId, vendorId, trx);
|
||||
// };
|
||||
// }
|
||||
// Write the vendor opening balance entries.
|
||||
await this.writeVendorOpeningBalance(vendorId, trx);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -9,10 +9,7 @@ import {
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { VendorsApplication } from './VendorsApplication.service';
|
||||
import {
|
||||
IVendorOpeningBalanceEditDTO,
|
||||
IVendorsFilter,
|
||||
} from './types/Vendors.types';
|
||||
import { VendorOpeningBalanceEditDto } from './dtos/VendorOpeningBalanceEdit.dto';
|
||||
import {
|
||||
ApiOperation,
|
||||
ApiResponse,
|
||||
@@ -68,7 +65,7 @@ export class VendorsController {
|
||||
@ApiOperation({ summary: 'Edit the given vendor opening balance.' })
|
||||
editOpeningBalance(
|
||||
@Param('id') vendorId: number,
|
||||
@Body() openingBalanceDTO: IVendorOpeningBalanceEditDTO,
|
||||
@Body() openingBalanceDTO: VendorOpeningBalanceEditDto,
|
||||
) {
|
||||
return this.vendorsApplication.editOpeningBalance(
|
||||
vendorId,
|
||||
|
||||
@@ -18,9 +18,14 @@ import { VendorsExportable } from './VendorsExportable';
|
||||
import { VendorsImportable } from './VendorsImportable';
|
||||
import { BulkDeleteVendorsService } from './BulkDeleteVendors.service';
|
||||
import { ValidateBulkDeleteVendorsService } from './ValidateBulkDeleteVendors.service';
|
||||
import { LedgerModule } from '../Ledger/Ledger.module';
|
||||
import { AccountsModule } from '../Accounts/Accounts.module';
|
||||
import { VendorGLEntries } from './VendorGLEntries';
|
||||
import { VendorGLEntriesStorage } from './VendorGLEntriesStorage';
|
||||
import { VendorsWriteGLOpeningSubscriber } from './subscribers/VendorGLEntriesSubscriber';
|
||||
|
||||
@Module({
|
||||
imports: [TenancyDatabaseModule, DynamicListModule],
|
||||
imports: [TenancyDatabaseModule, DynamicListModule, LedgerModule, AccountsModule],
|
||||
controllers: [VendorsController],
|
||||
providers: [
|
||||
ActivateVendorService,
|
||||
@@ -38,7 +43,10 @@ import { ValidateBulkDeleteVendorsService } from './ValidateBulkDeleteVendors.se
|
||||
TransformerInjectable,
|
||||
TenancyContext,
|
||||
VendorsExportable,
|
||||
VendorsImportable
|
||||
VendorsImportable,
|
||||
VendorGLEntries,
|
||||
VendorGLEntriesStorage,
|
||||
VendorsWriteGLOpeningSubscriber,
|
||||
],
|
||||
})
|
||||
export class VendorsModule {}
|
||||
export class VendorsModule { }
|
||||
|
||||
@@ -5,10 +5,7 @@ import { EditVendorService } from './commands/EditVendor.service';
|
||||
import { DeleteVendorService } from './commands/DeleteVendor.service';
|
||||
import { EditOpeningBalanceVendorService } from './commands/EditOpeningBalanceVendor.service';
|
||||
import { GetVendorService } from './queries/GetVendor';
|
||||
import {
|
||||
IVendorOpeningBalanceEditDTO,
|
||||
IVendorsFilter,
|
||||
} from './types/Vendors.types';
|
||||
import { VendorOpeningBalanceEditDto } from './dtos/VendorOpeningBalanceEdit.dto';
|
||||
import { GetVendorsService } from './queries/GetVendors.service';
|
||||
import { CreateVendorDto } from './dtos/CreateVendor.dto';
|
||||
import { EditVendorDto } from './dtos/EditVendor.dto';
|
||||
@@ -58,14 +55,14 @@ export class VendorsApplication {
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the opening balance of the given customer.
|
||||
* @param {number} vendorId
|
||||
* @param {IVendorOpeningBalanceEditDTO} openingBalanceEditDTO
|
||||
* Changes the opening balance of the given vendor.
|
||||
* @param {number} vendorId
|
||||
* @param {VendorOpeningBalanceEditDto} openingBalanceEditDTO
|
||||
* @returns {Promise<IVendor>}
|
||||
*/
|
||||
public editOpeningBalance(
|
||||
vendorId: number,
|
||||
openingBalanceEditDTO: IVendorOpeningBalanceEditDTO,
|
||||
openingBalanceEditDTO: VendorOpeningBalanceEditDto,
|
||||
) {
|
||||
return this.editOpeningBalanceService.editOpeningBalance(
|
||||
vendorId,
|
||||
@@ -95,10 +92,7 @@ export class VendorsApplication {
|
||||
vendorIds: number[],
|
||||
options?: { skipUndeletable?: boolean },
|
||||
) {
|
||||
return this.bulkDeleteVendorsService.bulkDeleteVendors(
|
||||
vendorIds,
|
||||
options,
|
||||
);
|
||||
return this.bulkDeleteVendorsService.bulkDeleteVendors(vendorIds, options);
|
||||
}
|
||||
|
||||
public validateBulkDeleteVendors(vendorIds: number[]) {
|
||||
|
||||
@@ -2,10 +2,10 @@ import { Knex } from 'knex';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { EventEmitter2 } from '@nestjs/event-emitter';
|
||||
import {
|
||||
IVendorOpeningBalanceEditDTO,
|
||||
IVendorOpeningBalanceEditedPayload,
|
||||
IVendorOpeningBalanceEditingPayload,
|
||||
} from '../types/Vendors.types';
|
||||
import { VendorOpeningBalanceEditDto } from '../dtos/VendorOpeningBalanceEdit.dto';
|
||||
import { UnitOfWork } from '@/modules/Tenancy/TenancyDB/UnitOfWork.service';
|
||||
import { Vendor } from '../models/Vendor';
|
||||
import { events } from '@/common/events/events';
|
||||
@@ -29,12 +29,12 @@ export class EditOpeningBalanceVendorService {
|
||||
/**
|
||||
* Changes the opening balance of the given customer.
|
||||
* @param {number} vendorId
|
||||
* @param {IVendorOpeningBalanceEditDTO} openingBalanceEditDTO
|
||||
* @param {VendorOpeningBalanceEditDto} openingBalanceEditDTO
|
||||
* @returns {Promise<IVendor>}
|
||||
*/
|
||||
public async editOpeningBalance(
|
||||
vendorId: number,
|
||||
openingBalanceEditDTO: IVendorOpeningBalanceEditDTO,
|
||||
openingBalanceEditDTO: VendorOpeningBalanceEditDto,
|
||||
) {
|
||||
// Retrieves the old vendor or throw not found error.
|
||||
const oldVendor = await this.vendorModel()
|
||||
|
||||
@@ -2,11 +2,13 @@ import { ApiProperty } from '@nestjs/swagger';
|
||||
import {
|
||||
IsISO8601,
|
||||
IsInt,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
Min,
|
||||
IsBoolean,
|
||||
IsEmail,
|
||||
IsString,
|
||||
ValidateIf,
|
||||
} from 'class-validator';
|
||||
import { ContactAddressDto } from '@/modules/Customers/dtos/ContactAddress.dto';
|
||||
import { IsOptional, ToNumber } from '@/common/decorators/Validators';
|
||||
@@ -30,8 +32,12 @@ export class CreateVendorDto extends ContactAddressDto {
|
||||
@ToNumber()
|
||||
openingBalanceExchangeRate?: number;
|
||||
|
||||
@ApiProperty({ required: false, description: 'Date of the opening balance' })
|
||||
@IsOptional()
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
description: 'Date of the opening balance (required when openingBalance is provided)',
|
||||
})
|
||||
@ValidateIf((o) => o.openingBalance != null)
|
||||
@IsNotEmpty({ message: 'openingBalanceAt is required when openingBalance is provided' })
|
||||
@IsISO8601()
|
||||
openingBalanceAt?: Date;
|
||||
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsOptional, ToNumber } from '@/common/decorators/Validators';
|
||||
|
||||
export class VendorOpeningBalanceEditDto {
|
||||
@ApiProperty({
|
||||
required: true,
|
||||
description: 'Opening balance',
|
||||
example: 5000.0,
|
||||
})
|
||||
@IsNumber()
|
||||
@IsNotEmpty()
|
||||
@ToNumber()
|
||||
openingBalance: number;
|
||||
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
description: 'Opening balance date',
|
||||
example: '2024-01-01',
|
||||
})
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
openingBalanceAt?: string;
|
||||
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
description: 'Opening balance exchange rate',
|
||||
example: 1.0,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@ToNumber()
|
||||
openingBalanceExchangeRate?: number;
|
||||
|
||||
@ApiProperty({
|
||||
required: false,
|
||||
description: 'Opening balance branch ID',
|
||||
example: 101,
|
||||
})
|
||||
@IsOptional()
|
||||
@IsNumber()
|
||||
@ToNumber()
|
||||
openingBalanceBranchId?: number;
|
||||
}
|
||||
@@ -36,6 +36,7 @@ export class Vendor extends TenantBaseModel {
|
||||
openingBalance: number;
|
||||
openingBalanceExchangeRate: number;
|
||||
openingBalanceAt: Date | string;
|
||||
openingBalanceBranchId?: number;
|
||||
|
||||
salutation: string;
|
||||
firstName: string;
|
||||
|
||||
@@ -1,91 +1,71 @@
|
||||
// import { Inject, Service } from 'typedi';
|
||||
// import events from '@/subscribers/events';
|
||||
// import { VendorGLEntriesStorage } from '../VendorGLEntriesStorage';
|
||||
// import {
|
||||
// IVendorEventCreatedPayload,
|
||||
// IVendorEventDeletedPayload,
|
||||
// IVendorOpeningBalanceEditedPayload,
|
||||
// } from '@/interfaces';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
import { events } from '@/common/events/events';
|
||||
import { VendorGLEntriesStorage } from '../VendorGLEntriesStorage';
|
||||
import {
|
||||
IVendorEventCreatedPayload,
|
||||
IVendorEventDeletedPayload,
|
||||
IVendorOpeningBalanceEditedPayload,
|
||||
} from '../types/Vendors.types';
|
||||
|
||||
// @Service()
|
||||
// export class VendorsWriteGLOpeningSubscriber {
|
||||
// @Inject()
|
||||
// private vendorGLEntriesStorage: VendorGLEntriesStorage;
|
||||
@Injectable()
|
||||
export class VendorsWriteGLOpeningSubscriber {
|
||||
constructor(
|
||||
private readonly vendorGLEntriesStorage: VendorGLEntriesStorage,
|
||||
) {}
|
||||
|
||||
// /**
|
||||
// * Constructor method.
|
||||
// */
|
||||
// public attach(bus) {
|
||||
// bus.subscribe(
|
||||
// events.vendors.onCreated,
|
||||
// this.handleWriteOpeningBalanceEntries
|
||||
// );
|
||||
// bus.subscribe(
|
||||
// events.vendors.onDeleted,
|
||||
// this.handleRevertOpeningBalanceEntries
|
||||
// );
|
||||
// bus.subscribe(
|
||||
// events.vendors.onOpeningBalanceChanged,
|
||||
// this.handleRewriteOpeningEntriesOnChanged
|
||||
// );
|
||||
// }
|
||||
/**
|
||||
* Writes the open balance journal entries once the vendor created.
|
||||
* @param {IVendorEventCreatedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.vendors.onCreated)
|
||||
public async handleWriteOpeningBalanceEntries({
|
||||
vendor,
|
||||
trx,
|
||||
}: IVendorEventCreatedPayload) {
|
||||
// Writes the vendor opening balance journal entries.
|
||||
if (vendor.openingBalance) {
|
||||
await this.vendorGLEntriesStorage.writeVendorOpeningBalance(
|
||||
vendor.id,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Writes the open balance journal entries once the vendor created.
|
||||
// * @param {IVendorEventCreatedPayload} payload -
|
||||
// */
|
||||
// private handleWriteOpeningBalanceEntries = async ({
|
||||
// tenantId,
|
||||
// vendor,
|
||||
// trx,
|
||||
// }: IVendorEventCreatedPayload) => {
|
||||
// // Writes the vendor opening balance journal entries.
|
||||
// if (vendor.openingBalance) {
|
||||
// await this.vendorGLEntriesStorage.writeVendorOpeningBalance(
|
||||
// tenantId,
|
||||
// vendor.id,
|
||||
// trx
|
||||
// );
|
||||
// }
|
||||
// };
|
||||
/**
|
||||
* Revert the opening balance journal entries once the vendor deleted.
|
||||
* @param {IVendorEventDeletedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.vendors.onDeleted)
|
||||
public async handleRevertOpeningBalanceEntries({
|
||||
vendorId,
|
||||
trx,
|
||||
}: IVendorEventDeletedPayload) {
|
||||
await this.vendorGLEntriesStorage.revertVendorOpeningBalance(
|
||||
vendorId,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Revert the opening balance journal entries once the vendor deleted.
|
||||
// * @param {IVendorEventDeletedPayload} payload -
|
||||
// */
|
||||
// private handleRevertOpeningBalanceEntries = async ({
|
||||
// tenantId,
|
||||
// vendorId,
|
||||
// trx,
|
||||
// }: IVendorEventDeletedPayload) => {
|
||||
// await this.vendorGLEntriesStorage.revertVendorOpeningBalance(
|
||||
// tenantId,
|
||||
// vendorId,
|
||||
// trx
|
||||
// );
|
||||
// };
|
||||
|
||||
// /**
|
||||
// * Handles the rewrite opening balance entries once opening balnace changed.
|
||||
// * @param {ICustomerOpeningBalanceEditedPayload} payload -
|
||||
// */
|
||||
// private handleRewriteOpeningEntriesOnChanged = async ({
|
||||
// tenantId,
|
||||
// vendor,
|
||||
// trx,
|
||||
// }: IVendorOpeningBalanceEditedPayload) => {
|
||||
// if (vendor.openingBalance) {
|
||||
// await this.vendorGLEntriesStorage.rewriteVendorOpeningBalance(
|
||||
// tenantId,
|
||||
// vendor.id,
|
||||
// trx
|
||||
// );
|
||||
// } else {
|
||||
// await this.vendorGLEntriesStorage.revertVendorOpeningBalance(
|
||||
// tenantId,
|
||||
// vendor.id,
|
||||
// trx
|
||||
// );
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
/**
|
||||
* Handles the rewrite opening balance entries once opening balance changed.
|
||||
* @param {IVendorOpeningBalanceEditedPayload} payload -
|
||||
*/
|
||||
@OnEvent(events.vendors.onOpeningBalanceChanged)
|
||||
public async handleRewriteOpeningEntriesOnChanged({
|
||||
vendor,
|
||||
trx,
|
||||
}: IVendorOpeningBalanceEditedPayload) {
|
||||
if (vendor.openingBalance) {
|
||||
await this.vendorGLEntriesStorage.rewriteVendorOpeningBalance(
|
||||
vendor.id,
|
||||
trx,
|
||||
);
|
||||
} else {
|
||||
await this.vendorGLEntriesStorage.revertVendorOpeningBalance(
|
||||
vendor.id,
|
||||
trx,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { IDynamicListFilter } from '@/modules/DynamicListing/DynamicFilter/Dynam
|
||||
import { IFilterMeta, IPaginationMeta } from '@/interfaces/Model';
|
||||
import { CreateVendorDto } from '../dtos/CreateVendor.dto';
|
||||
import { EditVendorDto } from '../dtos/EditVendor.dto';
|
||||
import { VendorOpeningBalanceEditDto } from '../dtos/VendorOpeningBalanceEdit.dto';
|
||||
|
||||
// ----------------------------------
|
||||
export interface IVendorNewDTO extends IContactAddressDTO {
|
||||
@@ -92,23 +93,16 @@ export interface IVendorEventEditedPayload {
|
||||
trx?: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IVendorOpeningBalanceEditDTO {
|
||||
openingBalance: number;
|
||||
openingBalanceAt: Date | string;
|
||||
openingBalanceExchangeRate: number;
|
||||
openingBalanceBranchId?: number;
|
||||
}
|
||||
|
||||
export interface IVendorOpeningBalanceEditingPayload {
|
||||
oldVendor: Vendor;
|
||||
openingBalanceEditDTO: IVendorOpeningBalanceEditDTO;
|
||||
openingBalanceEditDTO: VendorOpeningBalanceEditDto;
|
||||
trx?: Knex.Transaction;
|
||||
}
|
||||
|
||||
export interface IVendorOpeningBalanceEditedPayload {
|
||||
vendor: Vendor;
|
||||
oldVendor: Vendor;
|
||||
openingBalanceEditDTO: IVendorOpeningBalanceEditDTO;
|
||||
openingBalanceEditDTO: VendorOpeningBalanceEditDto;
|
||||
trx: Knex.Transaction;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,47 @@
|
||||
FROM node:18.16.0-alpine as build
|
||||
|
||||
USER root
|
||||
# Stage 1: Build
|
||||
FROM node:18.16.0-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy application dependency manifests to the container image.
|
||||
COPY . .
|
||||
# Install pnpm
|
||||
RUN npm install -g pnpm@8.10.2
|
||||
|
||||
# Install application dependencies
|
||||
RUN apk update
|
||||
RUN apk add python3 build-base chromium
|
||||
# Install build dependencies
|
||||
RUN apk add --no-cache python3 build-base chromium
|
||||
|
||||
# Set PYHTON env
|
||||
# Set Python environment
|
||||
ENV PYTHON=/usr/bin/python3
|
||||
|
||||
# Install pnpm packages dependencies
|
||||
RUN npm install -g pnpm
|
||||
# Copy package files for dependency installation
|
||||
COPY --chown=node:node package.json pnpm-lock.yaml pnpm-workspace.yaml lerna.json ./
|
||||
COPY --chown=node:node packages/webapp/package.json ./packages/webapp/
|
||||
COPY --chown=node:node shared/bigcapital-utils/package.json ./shared/bigcapital-utils/
|
||||
COPY --chown=node:node shared/pdf-templates/package.json ./shared/pdf-templates/
|
||||
COPY --chown=node:node shared/email-components/package.json ./shared/email-components/
|
||||
|
||||
# Install all dependencies (including devDependencies for build)
|
||||
RUN pnpm install
|
||||
|
||||
# Copy source code for webapp and dependencies
|
||||
COPY --chown=node:node ./packages/webapp ./packages/webapp
|
||||
COPY --chown=node:node ./shared/bigcapital-utils ./shared/bigcapital-utils
|
||||
COPY --chown=node:node ./shared/pdf-templates ./shared/pdf-templates
|
||||
COPY --chown=node:node ./shared/email-components ./shared/email-components
|
||||
|
||||
# Build webapp package
|
||||
RUN pnpm run build:webapp
|
||||
|
||||
FROM nginx
|
||||
# Stage 2: Nginx
|
||||
FROM nginx:alpine
|
||||
|
||||
COPY ./packages/webapp/nginx/sites/default.conf /etc/nginx/conf.d/default.conf
|
||||
COPY --from=build /app/packages/webapp/dist /usr/share/nginx/html
|
||||
# Copy nginx configuration
|
||||
COPY --chown=root:root ./packages/webapp/nginx/sites/default.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Copy built webapp assets from builder stage
|
||||
COPY --from=builder --chown=nginx:nginx /app/packages/webapp/dist /usr/share/nginx/html
|
||||
|
||||
# Expose port
|
||||
EXPOSE 80
|
||||
|
||||
# Nginx runs as nginx user by default, which is good for security
|
||||
# No CMD needed as nginx base image already has it
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"@blueprintjs/colors": "4.1.19",
|
||||
"@blueprintjs/core": "^4.20.2",
|
||||
"@blueprintjs/datetime": "^4.4.37",
|
||||
"@blueprintjs/datetime2": "^3.0.10",
|
||||
"@blueprintjs/datetime2": "^0.9.0",
|
||||
"@blueprintjs/popover2": "^1.14.11",
|
||||
"@blueprintjs/select": "^4.9.24",
|
||||
"@blueprintjs/table": "^4.10.12",
|
||||
|
||||
@@ -4,7 +4,12 @@ import styled from 'styled-components';
|
||||
import { DataTable } from '../Datatable';
|
||||
|
||||
export const ReportDataTable = styled(DataTable)`
|
||||
--x-table-no-results-border-color: #ddd;
|
||||
|
||||
.bp4-dark & {
|
||||
--x-table-no-results-border-color: var(--color-dark-gray5);
|
||||
}
|
||||
.table .tbody .tr.no-results:last-of-type .td {
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-bottom: 1px solid var(--x-table-no-results-border-color);
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
import { Menu, MenuItem, MenuDivider } from '@blueprintjs/core';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { FormattedMessage as T } from '@/components';
|
||||
import preferencesMenu from '@/constants/preferencesMenu';
|
||||
import { PreferencesMenu } from '@/constants/preferencesMenu';
|
||||
import PreferencesSidebarContainer from './PreferencesSidebarContainer';
|
||||
|
||||
import '@/style/pages/Preferences/Sidebar.scss';
|
||||
@@ -15,7 +15,7 @@ export default function PreferencesSidebar() {
|
||||
const history = useHistory();
|
||||
const location = useLocation();
|
||||
|
||||
const items = preferencesMenu.map((item) =>
|
||||
const items = PreferencesMenu.map((item) =>
|
||||
item.divider ? (
|
||||
<MenuDivider title={item.title} />
|
||||
) : (
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export default [
|
||||
export const AllocateLandedCostType = [
|
||||
{ name: intl.get('bills'), value: 'Bill' },
|
||||
{ name: intl.get('expenses'), value: 'Expense' },
|
||||
];
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// @ts-nocheck
|
||||
export default {
|
||||
export const App = {
|
||||
"app_name": "BigCapital",
|
||||
"app_version": "0.0.1 (build 12344)",
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
import React from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
|
||||
export default [
|
||||
export const ContactsOptions = [
|
||||
{ name: intl.get('customer'), path: 'customers' },
|
||||
{ name: intl.get('vendor'), path: 'vendors' },
|
||||
];
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
VendorAction,
|
||||
} from './abilityOption';
|
||||
|
||||
export default [
|
||||
export const KeyboardShortcutsOptions = [
|
||||
{
|
||||
shortcut_key: 'Shift + I',
|
||||
description: intl.get('jump_to_the_invoices'),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage as T } from '@/components';
|
||||
|
||||
export default [
|
||||
export const PreferencesMenu = [
|
||||
{
|
||||
text: <T id={'general'} />,
|
||||
disabled: false,
|
||||
@@ -13,10 +13,10 @@ export default [
|
||||
disabled: false,
|
||||
href: '/preferences/branding',
|
||||
},
|
||||
{
|
||||
text: 'Billing',
|
||||
href: '/preferences/billing',
|
||||
},
|
||||
// {
|
||||
// text: 'Billing',
|
||||
// href: '/preferences/billing',
|
||||
// },
|
||||
{
|
||||
text: <T id={'users'} />,
|
||||
href: '/preferences/users',
|
||||
@@ -63,11 +63,11 @@ export default [
|
||||
disabled: false,
|
||||
href: '/preferences/items',
|
||||
},
|
||||
{
|
||||
text: 'Integrations',
|
||||
disabled: false,
|
||||
href: '/preferences/integrations'
|
||||
},
|
||||
// {
|
||||
// text: 'Integrations',
|
||||
// disabled: false,
|
||||
// href: '/preferences/integrations'
|
||||
// },
|
||||
{
|
||||
text: 'API Keys',
|
||||
disabled: false,
|
||||
|
||||
@@ -8,6 +8,11 @@ import { If, AppToaster } from '@/components';
|
||||
import { NormalCell, BalanceCell, BankBalanceCell } from './components';
|
||||
import { transformTableStateToQuery, isBlank } from '@/utils';
|
||||
|
||||
export const DeleteAccountTypeError = {
|
||||
AccountPredefined: 'account_predefined',
|
||||
AccountHasAssociatedTransactions: 'account_has_associated_transactions',
|
||||
};
|
||||
|
||||
/**
|
||||
* Account name accessor.
|
||||
*/
|
||||
@@ -26,13 +31,13 @@ export const accountNameAccessor = (account) => {
|
||||
* Handle delete errors in bulk and singular.
|
||||
*/
|
||||
export const handleDeleteErrors = (errors) => {
|
||||
if (errors.find((e) => e.type === 'ACCOUNT.PREDEFINED')) {
|
||||
if (errors.find((e) => e.type === DeleteAccountTypeError.AccountPredefined)) {
|
||||
AppToaster.show({
|
||||
message: intl.get('cannot_delete_predefined_accounts'),
|
||||
intent: Intent.DANGER,
|
||||
});
|
||||
}
|
||||
if (errors.find((e) => e.type === 'ACCOUNT.HAS.ASSOCIATED.TRANSACTIONS')) {
|
||||
if (errors.find((e) => e.type === DeleteAccountTypeError.AccountHasAssociatedTransactions)) {
|
||||
AppToaster.show({
|
||||
message: intl.get('cannot_delete_account_has_associated_transactions'),
|
||||
intent: Intent.DANGER,
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Intent, Alert } from '@blueprintjs/core';
|
||||
import { queryCache } from 'react-query';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { FormattedMessage as T, AppToaster } from '@/components';
|
||||
|
||||
import { withAlertStoreConnect } from '@/containers/Alert/withAlertStoreConnect';
|
||||
@@ -22,6 +22,7 @@ function AccountBulkActivateAlert({
|
||||
requestBulkActivateAccounts,
|
||||
}) {
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const queryClient = useQueryClient();
|
||||
const selectedRowsCount = 0;
|
||||
|
||||
// Handle alert cancel.
|
||||
@@ -38,9 +39,9 @@ function AccountBulkActivateAlert({
|
||||
message: intl.get('the_accounts_has_been_successfully_activated'),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
queryCache.invalidateQueries('accounts-table');
|
||||
queryClient.invalidateQueries('accounts-table');
|
||||
})
|
||||
.catch((errors) => { })
|
||||
.catch((errors) => {})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
closeAlert(name);
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, { useState } from 'react';
|
||||
import { FormattedMessage as T } from '@/components';
|
||||
import intl from 'react-intl-universal';
|
||||
import { Intent, Alert } from '@blueprintjs/core';
|
||||
import { queryCache } from 'react-query';
|
||||
import { useQueryClient } from 'react-query';
|
||||
import { AppToaster } from '@/components';
|
||||
|
||||
// import { withAccountsActions } from '@/containers/Accounts/withAccountsTableActions';
|
||||
@@ -22,8 +22,8 @@ function AccountBulkInactivateAlert({
|
||||
|
||||
closeAlert,
|
||||
}) {
|
||||
|
||||
const [isLoading, setLoading] = useState(false);
|
||||
const queryClient = useQueryClient();
|
||||
const selectedRowsCount = 0;
|
||||
|
||||
// Handle alert cancel.
|
||||
@@ -39,9 +39,9 @@ function AccountBulkInactivateAlert({
|
||||
message: intl.get('the_accounts_have_been_successfully_inactivated'),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
queryCache.invalidateQueries('accounts-table');
|
||||
queryClient.invalidateQueries('accounts-table');
|
||||
})
|
||||
.catch((errors) => { })
|
||||
.catch((errors) => {})
|
||||
.finally(() => {
|
||||
setLoading(false);
|
||||
closeAlert(name);
|
||||
|
||||
@@ -3,7 +3,7 @@ import React, { useCallback } from 'react';
|
||||
import intl from 'react-intl-universal';
|
||||
import { AppToaster, FormattedMessage as T } from '@/components';
|
||||
import { Intent, Alert } from '@blueprintjs/core';
|
||||
import { queryCache } from 'react-query';
|
||||
import { useQueryClient } from 'react-query';
|
||||
|
||||
import { useApproveEstimate } from '@/hooks/query';
|
||||
|
||||
@@ -25,6 +25,7 @@ function EstimateApproveAlert({
|
||||
// #withAlertActions
|
||||
closeAlert,
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
const { mutateAsync: deliverEstimateMutate, isLoading } =
|
||||
useApproveEstimate();
|
||||
|
||||
@@ -40,7 +41,7 @@ function EstimateApproveAlert({
|
||||
message: intl.get('the_estimate_has_been_approved_successfully'),
|
||||
intent: Intent.SUCCESS,
|
||||
});
|
||||
queryCache.invalidateQueries('estimates-table');
|
||||
queryClient.invalidateQueries('estimates-table');
|
||||
})
|
||||
.catch((error) => {})
|
||||
.finally(() => {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { FormattedMessage as T, If, FFormGroup, FSelect, FRadioGroup, FInputGrou
|
||||
import { handleStringChange } from '@/utils';
|
||||
import { FieldRequiredHint } from '@/components';
|
||||
import { CLASSES } from '@/constants/classes';
|
||||
import allocateLandedCostType from '@/constants/allocateLandedCostType';
|
||||
import { AllocateLandedCostType } from '@/constants/allocateLandedCostType';
|
||||
|
||||
import AllocateLandedCostFormBody from './AllocateLandedCostFormBody';
|
||||
import {
|
||||
@@ -81,7 +81,7 @@ export default function AllocateLandedCostFormFields() {
|
||||
>
|
||||
<FSelect
|
||||
name={'transaction_type'}
|
||||
items={allocateLandedCostType}
|
||||
items={AllocateLandedCostType}
|
||||
onItemChange={handleTransactionTypeChange}
|
||||
filterable={false}
|
||||
valueAccessor={'value'}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// @ts-nocheck
|
||||
import React from 'react';
|
||||
import { DialogContent } from '@/components';
|
||||
import { useQuery, queryCache } from 'react-query';
|
||||
import { useQuery, useQueryClient } from 'react-query';
|
||||
|
||||
import ReferenceNumberForm from '@/containers/JournalNumber/ReferenceNumberForm';
|
||||
|
||||
@@ -31,6 +31,7 @@ function BillNumberDialogContent({
|
||||
// #withBillsActions
|
||||
setBillNumberChanged,
|
||||
}) {
|
||||
const queryClient = useQueryClient();
|
||||
const fetchSettings = useQuery(['settings'], () => requestFetchOptions({}));
|
||||
|
||||
const handleSubmitForm = (values, { setSubmitting }) => {
|
||||
@@ -45,7 +46,7 @@ function BillNumberDialogContent({
|
||||
setBillNumberChanged(true);
|
||||
|
||||
setTimeout(() => {
|
||||
queryCache.invalidateQueries('settings');
|
||||
queryClient.invalidateQueries('settings');
|
||||
}, 250);
|
||||
})
|
||||
.catch(() => {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { FormattedMessage as T } from '@/components';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { useContactDuplicateFromContext } from './ContactDuplicateProvider';
|
||||
|
||||
import Contacts from '@/constants/contactsOptions';
|
||||
import { ContactsOptions } from '@/constants/contactsOptions';
|
||||
|
||||
import { withDialogActions } from '@/containers/Dialog/withDialogActions';
|
||||
import { compose } from '@/utils';
|
||||
@@ -66,7 +66,7 @@ function ContactDuplicateForm({
|
||||
>
|
||||
<FSelect
|
||||
name={'contact_type'}
|
||||
items={Contacts}
|
||||
items={ContactsOptions}
|
||||
placeholder={<T id={'select_contact'} />}
|
||||
textAccessor={'name'}
|
||||
valueAccessor={'path'}
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
FeatureCan,
|
||||
InputPrependText,
|
||||
} from '@/components';
|
||||
import { FMoneyInputGroup, FFormGroup } from '@/components/Forms';
|
||||
import { FMoneyInputGroup, FFormGroup, FDateInput } from '@/components/Forms';
|
||||
|
||||
import { useCustomerOpeningBalanceContext } from './CustomerOpeningBalanceFormProvider';
|
||||
import { useSetPrimaryBranchToForm } from './utils';
|
||||
@@ -59,28 +59,24 @@ function CustomerOpeningBalanceFields({
|
||||
</FFormGroup>
|
||||
|
||||
{/*------------ Opening balance at -----------*/}
|
||||
<FastField name={'opening_balance_at'}>
|
||||
{({ form, field: { value } }) => (
|
||||
<FormGroup
|
||||
label={
|
||||
<T id={'customer_opening_balance.label.opening_balance_at'} />
|
||||
}
|
||||
className={Classes.FILL}
|
||||
>
|
||||
<DateInput
|
||||
{...momentFormatter('YYYY/MM/DD')}
|
||||
onChange={handleDateChange((formattedDate) => {
|
||||
form.setFieldValue('opening_balance_at', formattedDate);
|
||||
})}
|
||||
value={tansformDateValue(value)}
|
||||
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
||||
inputProps={{
|
||||
leftIcon: <Icon icon={'date-range'} />,
|
||||
}}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
</FastField>
|
||||
<FFormGroup
|
||||
name={'opening_balance_at'}
|
||||
label={<T id={'customer_opening_balance.label.opening_balance_at'} />}
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<FDateInput
|
||||
name={'opening_balance_at'}
|
||||
formatDate={(date) => date.toLocaleDateString()}
|
||||
parseDate={(str) => new Date(str)}
|
||||
popoverProps={{ position: Position.BOTTOM, minimal: true }}
|
||||
inputProps={{
|
||||
leftIcon: <Icon icon={'date-range'} />,
|
||||
}}
|
||||
fill
|
||||
fastField
|
||||
/>
|
||||
</FFormGroup>
|
||||
|
||||
<If condition={!isEqual(base_currency, customer.currency_code)}>
|
||||
{/*------------ Opening balance exchange rate -----------*/}
|
||||
@@ -99,12 +95,15 @@ function CustomerOpeningBalanceFields({
|
||||
<FFormGroup
|
||||
label={<T id={'branch'} />}
|
||||
name={'opening_balance_branch_id'}
|
||||
className={classNames('form-group--select-list', Classes.FILL)}
|
||||
fill
|
||||
fastField
|
||||
>
|
||||
<BranchSelect
|
||||
name={'opening_balance_branch_id'}
|
||||
branches={branches}
|
||||
popoverProps={{ minimal: true }}
|
||||
fastField
|
||||
fill
|
||||
/>
|
||||
</FFormGroup>
|
||||
</FeatureCan>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user