Compare commits

..

43 Commits

Author SHA1 Message Date
QT
bd9fdeb3d9 Update docker-compose.prod.yml
Some checks failed
Build and Deploy Develop Docker Container / Build and deploy webapp container (push) Has been cancelled
Build and Deploy Develop Docker Container / Build and deploy server container (push) Has been cancelled
2026-02-06 17:11:02 +00:00
QT
6c633de22b Update docker-compose.prod.yml
Some checks failed
Build and Deploy Develop Docker Container / Build and deploy webapp container (push) Has been cancelled
Build and Deploy Develop Docker Container / Build and deploy server container (push) Has been cancelled
2026-02-06 17:01:51 +00:00
QT
f64c59b9a1 Update docker-compose.prod.yml
Some checks failed
Build and Deploy Develop Docker Container / Build and deploy webapp container (push) Has been cancelled
Build and Deploy Develop Docker Container / Build and deploy server container (push) Has been cancelled
2026-02-06 16:52:03 +00:00
QT
a4adb4fb03 Update docker-compose.prod.yml
Some checks failed
Build and Deploy Develop Docker Container / Build and deploy webapp container (push) Has been cancelled
Build and Deploy Develop Docker Container / Build and deploy server container (push) Has been cancelled
agregar network coolify
2026-02-06 15:47:48 +00:00
QT
985708e87a Update docker-compose.prod.yml
Some checks failed
Build and Deploy Develop Docker Container / Build and deploy webapp container (push) Has been cancelled
Build and Deploy Develop Docker Container / Build and deploy server container (push) Has been cancelled
agregar worker
2026-02-06 14:21:29 +00:00
QT
01cef7a595 Update docker-compose.prod.yml
Some checks failed
Build and Deploy Develop Docker Container / Build and deploy webapp container (push) Has been cancelled
Build and Deploy Develop Docker Container / Build and deploy server container (push) Has been cancelled
2026-02-06 13:35:13 +00:00
QT
18ab1f6ae3 Merge branch 'main' into develop
Some checks failed
Build and Deploy Develop Docker Container / Build and deploy webapp container (push) Has been cancelled
Build and Deploy Develop Docker Container / Build and deploy server container (push) Has been cancelled
2026-02-05 23:18:49 +00:00
QT
5ea3ac14e4 Update docker-compose.prod.yml
Some checks failed
Build and Deploy Develop Docker Container / Build and deploy webapp container (push) Has been cancelled
Build and Deploy Develop Docker Container / Build and deploy server container (push) Has been cancelled
2026-02-05 23:16:27 +00:00
QT
84079df772 Merge pull request 'develop' (#1) from develop into main
Some checks failed
E2E / Test setup (push) Has been cancelled
E2E / Playwright tests (push) Has been cancelled
TypeCheck / TypeScript Type Check (push) Has been cancelled
Reviewed-on: #1
2026-02-05 23:10:08 +00:00
QT
28a8b0b5d1 Update docker-compose.prod.yml
Some checks failed
Build and Deploy Develop Docker Container / Build and deploy webapp container (push) Has been cancelled
Build and Deploy Develop Docker Container / Build and deploy server container (push) Has been cancelled
E2E / Test setup (pull_request) Has been cancelled
E2E / Playwright tests (pull_request) Has been cancelled
TypeCheck / TypeScript Type Check (pull_request) Has been cancelled
2026-02-05 23:01:31 +00:00
Ahmed Bouhuolia
2c05785096 Merge pull request #934 from bigcapitalhq/fix/branches-activation-bills
fix(server): branches activation not marking bills and payments with primary branch
2026-02-05 16:06:39 +02:00
Ahmed Bouhuolia
6af4be9c6c fix(server): branches activation not marking bills and payments with primary branch
When activating the multi-branches feature, existing bills, vendor credits,
and bill payments were not being marked with the default primary branch.

Changes:
- Add missing @Inject decorators to BillActivateBranches, VendorCreditActivateBranches,
  and BillPaymentsActivateBranches services
- Create BillBranchesActivateSubscriber to listen to onActivated event
- Create VendorCreditBranchesActivateSubscriber to listen to onActivated event
- Register BillPaymentsActivateBranches and PaymentMadeActivateBranchesSubscriber
  in BranchesModule
- Add branch object to BillResponseDto for API responses
- Add branch to BillTransformer includeAttributes

Fixes: #935
2026-02-05 16:03:57 +02:00
Ahmed Bouhuolia
8def1d31d2 Merge pull request #933 from bigcapitalhq/20260205-151219-7770
feat(webapp): add blurry background to sticky data table cells
2026-02-05 15:29:47 +02:00
Ahmed Bouhuolia
afab02a053 feat(webapp): add blurry background to sticky data table cells
Add backdrop-filter blur effect to sticky column cells in financial reports
to prevent content from showing through during horizontal scrolling.
The effect only applies when rows are not hovered to preserve hover
background interactions.
2026-02-05 15:27:45 +02:00
Ahmed Bouhuolia
8e925c62f2 Merge pull request #932 from bigcapitalhq/20260205-151219-7770
fix(server): balance sheet query validation schema
2026-02-05 15:14:45 +02:00
Ahmed Bouhuolia
1b7d513adf fix(server): balance sheet query validation schema 2026-02-05 15:12:54 +02:00
Ahmed Bouhuolia
7d764fb390 Merge pull request #931 from bigcapitalhq/fix/item-error-handling
fix(items): correct error type handling and add swagger documentation
2026-02-04 21:44:45 +02:00
Ahmed Bouhuolia
c571f50a74 fix(items): correct error type handling and add swagger documentation
- Fix error type mismatch: change 'ITEM.NAME.ALREADY.EXISTS' to 'ITEM_NAME_EXISTS'
- Add ItemErrorType constant with UpperCamelCase keys for better maintainability
- Update all error checks to use the new ItemErrorType constant
- Add ItemErrorResponse.dto.ts with documented error types for swagger
- Add @ApiResponse decorators to document 400 validation errors in swagger
2026-02-04 21:42:39 +02:00
Ahmed Bouhuolia
6549026344 Merge pull request #930 from bigcapitalhq/fix/account-delete-error-handling
fix(webapp): account delete error handling response types
2026-02-04 21:31:38 +02:00
Ahmed Bouhuolia
0963394b04 fix(webapp): account delete error handling response types 2026-02-04 21:27:25 +02:00
Ahmed Bouhuolia
6cab0651fc Merge pull request #927 from bigcapitalhq/feature/20260202223150
fix(webapp): darkmode warehouses list page
2026-02-02 22:36:42 +02:00
Ahmed Bouhuolia
4af537d6dd fix(webapp): darkmode warehouses list page 2026-02-02 22:31:53 +02:00
Ahmed Bouhuolia
34db64612c Merge pull request #926 from bigcapitalhq/20260202-185120-9c84
fix(webapp): constrant not found row color
2026-02-02 18:53:48 +02:00
Ahmed Bouhuolia
10225bbfed fix(webapp): constrant not found row color 2026-02-02 18:51:52 +02:00
Ahmed Bouhuolia
c3a4fe6b37 Merge pull request #924 from bigcapitalhq/20260201-180532-f578
fix(webapp): normalize api path
2026-02-01 18:06:51 +02:00
Ahmed Bouhuolia
02be959461 fix(webapp): normalize api path 2026-02-01 18:05:51 +02:00
Ahmed Bouhuolia
d5bf56e333 Merge pull request #923 from bigcapitalhq/20260201-165255-f063
fix(server): copy .js migration files
2026-02-01 16:56:49 +02:00
Ahmed Bouhuolia
e3182c15b3 fix(server): copy .js migration files 2026-02-01 16:53:21 +02:00
Ahmed Bouhuolia
dfa63ece21 Merge pull request #921 from bigcapitalhq/20260131-145158-fd0c
fix(scripts): db migration dockerfile
2026-01-31 15:32:07 +02:00
Ahmed Bouhuolia
6e95bd7da1 fix(scripts): db migration dockerfile 2026-01-31 15:31:17 +02:00
Ahmed Bouhuolia
f51fffa5c7 Merge pull request #918 from bigcapitalhq/20260129-203653-75b0
feat(server): add bull ui board
2026-01-29 20:39:05 +02:00
Ahmed Bouhuolia
6193358cc3 feat(server): add bull ui board 2026-01-29 20:37:04 +02:00
Ahmed Bouhuolia
518abcd30d Merge pull request #917 from bigcapitalhq/20260128-195652-2287
fix: dockerfile build script
2026-01-28 23:42:24 +02:00
Ahmed Bouhuolia
7874b9f765 fix(ci): dockerfile build script 2026-01-28 23:40:32 +02:00
Ahmed Bouhuolia
02cc7e0c96 Merge pull request #916 from bigcapitalhq/20260128-181425-8b6a
fix(webapp): blueprintjs datetime version
2026-01-28 18:17:29 +02:00
Ahmed Bouhuolia
57cc513873 fix(webapp): blueprintjs datetime version 2026-01-28 18:14:44 +02:00
Ahmed Bouhuolia
f5bfdede30 Merge pull request #915 from bigcapitalhq/fix-vendor-customer-edit-opening-balance
fix(webapp): vendor/customer edit opening balance
2026-01-27 22:09:00 +02:00
Ahmed Bouhuolia
488556bb59 fix(webapp): vendor/customer edit opening balance 2026-01-27 22:06:57 +02:00
Ahmed Bouhuolia
0fc5a66e95 Merge pull request #914 from bigcapitalhq/fix-costable-inventory-transactions
fix(server): costable attr of inventory gl entries
2026-01-26 15:02:35 +02:00
Ahmed Bouhuolia
d9ae51027e fix(server): costable attr of inventory gl entries 2026-01-26 15:00:17 +02:00
Ahmed Bouhuolia
a92d6112d9 Merge pull request #913 from bigcapitalhq/feature/20260125222025
fix(server): sale receipt cost gl entries
2026-01-25 22:22:08 +02:00
Ahmed Bouhuolia
889b0cec4b fix(server): sale receipt cost gl entries 2026-01-25 22:20:28 +02:00
Ahmed Bouhuolia
1c4c41ebba Merge pull request #912 from bigcapitalhq/feature/20260125215941
fix(server): mark compute inventory cost flag
2026-01-25 22:02:13 +02:00
83 changed files with 1198 additions and 636 deletions

93
.dockerignore Normal file
View 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

View File

@@ -9,21 +9,21 @@ services:
- server
- webapp
ports:
- '${PUBLIC_PROXY_PORT:-80}:80'
- '${PUBLIC_PROXY_SSL_PORT:-443}:443'
- '8085:80'
- '8443:443'
tty: true
volumes:
- ./docker/envoy/envoy.yaml:/etc/envoy/envoy.yaml
restart: on-failure
networks:
- bigcapital_network
- coolify
webapp:
container_name: bigcapital-webapp
image: bigcapitalhq/webapp:latest
restart: on-failure
networks:
- bigcapital_network
- coolify
server:
container_name: bigcapital-server
@@ -38,7 +38,7 @@ services:
- redis
restart: on-failure
networks:
- bigcapital_network
- coolify
environment:
# Mail
- MAIL_HOST=${MAIL_HOST}
@@ -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}
@@ -138,8 +144,8 @@ services:
depends_on:
- mysql
networks:
- bigcapital_network
- coolify
mysql:
container_name: bigcapital-mysql
restart: on-failure
@@ -152,10 +158,12 @@ services:
- MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
volumes:
- mysql:/var/lib/mysql
ports:
- '3335:3306'
expose:
- '3306'
networks:
- bigcapital_network
- coolify
redis:
container_name: bigcapital-redis
@@ -167,14 +175,14 @@ services:
volumes:
- redis:/data
networks:
- bigcapital_network
- coolify
gotenberg:
image: gotenberg/gotenberg:7
expose:
- '9000'
networks:
- bigcapital_network
- coolify
# Volumes
volumes:
@@ -188,5 +196,5 @@ volumes:
# Networks
networks:
bigcapital_network:
driver: bridge
coolify:
external: true

View File

@@ -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"

View File

@@ -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" ]

View File

@@ -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"
}
}
}
}

View File

@@ -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",

View 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,
}));

View File

@@ -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,
];

View 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,
}));

View 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();
};
}

View File

@@ -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;
/**

View File

@@ -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],

View File

@@ -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 {}

View File

@@ -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(

View File

@@ -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(

View File

@@ -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 {}

View File

@@ -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;

View File

@@ -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: [

View File

@@ -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;

View File

@@ -1,6 +1,8 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { ItemEntryDto } from '@/modules/TransactionItemEntry/dto/ItemEntry.dto';
import { AttachmentLinkDto } from '@/modules/Attachments/dtos/Attachment.dto';
import { BranchResponseDto } from '@/modules/Branches/dtos/BranchResponse.dto';
import { DiscountType } from '@/common/types/Discount';
export class BillResponseDto {
@@ -89,6 +91,14 @@ export class BillResponseDto {
})
branchId?: number;
@ApiProperty({
description: 'Branch details',
type: () => BranchResponseDto,
required: false,
})
@Type(() => BranchResponseDto)
branch?: BranchResponseDto;
@ApiProperty({
description: 'The ID of the project',
example: 301,

View File

@@ -30,6 +30,7 @@ export class BillTransformer extends Transformer {
'taxes',
'entries',
'attachments',
'branch',
];
};

View File

@@ -31,6 +31,12 @@ import { ValidateBranchExistance } from './integrations/ValidateBranchExistance'
import { ManualJournalBranchesValidator } from './integrations/ManualJournals/ManualJournalsBranchesValidator';
import { CashflowTransactionsActivateBranches } from './integrations/Cashflow/CashflowActivateBranches';
import { ExpensesActivateBranches } from './integrations/Expense/ExpensesActivateBranches';
import { BillActivateBranches } from './integrations/Purchases/BillBranchesActivate';
import { VendorCreditActivateBranches } from './integrations/Purchases/VendorCreditBranchesActivate';
import { BillPaymentsActivateBranches } from './integrations/Purchases/PaymentMadeBranchesActivate';
import { BillBranchesActivateSubscriber } from './subscribers/Activate/BillBranchesActivateSubscriber';
import { VendorCreditBranchesActivateSubscriber } from './subscribers/Activate/VendorCreditBranchesActivateSubscriber';
import { PaymentMadeActivateBranchesSubscriber } from './subscribers/Activate/PaymentMadeBranchesActivateSubscriber';
import { FeaturesModule } from '../Features/Features.module';
@Module({
@@ -66,7 +72,13 @@ import { FeaturesModule } from '../Features/Features.module';
ValidateBranchExistance,
ManualJournalBranchesValidator,
CashflowTransactionsActivateBranches,
ExpensesActivateBranches
ExpensesActivateBranches,
BillActivateBranches,
VendorCreditActivateBranches,
BillPaymentsActivateBranches,
BillBranchesActivateSubscriber,
VendorCreditBranchesActivateSubscriber,
PaymentMadeActivateBranchesSubscriber
],
exports: [
BranchesSettingsService,

View File

@@ -1,11 +1,14 @@
import { Knex } from 'knex';
import { Injectable } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { Bill } from '@/modules/Bills/models/Bill';
@Injectable()
export class BillActivateBranches {
constructor(private readonly billModel: TenantModelProxy<typeof Bill>) {}
constructor(
@Inject(Bill.name)
private readonly billModel: TenantModelProxy<typeof Bill>,
) {}
/**
* Updates all bills transactions with the primary branch.
@@ -17,7 +20,7 @@ export class BillActivateBranches {
primaryBranchId: number,
trx?: Knex.Transaction,
) => {
// Updates the sale invoice with primary branch.
await Bill.query(trx).update({ branchId: primaryBranchId });
// Updates the bills with primary branch.
await this.billModel().query(trx).update({ branchId: primaryBranchId });
};
}

View File

@@ -1,11 +1,12 @@
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { BillPayment } from '@/modules/BillPayments/models/BillPayment';
import { Injectable } from '@nestjs/common';
@Injectable()
export class BillPaymentsActivateBranches {
constructor(
@Inject(BillPayment.name)
private readonly billPaymentModel: TenantModelProxy<typeof BillPayment>,
) {}

View File

@@ -1,11 +1,12 @@
import { Knex } from 'knex';
import { Injectable } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { VendorCredit } from '@/modules/VendorCredit/models/VendorCredit';
@Injectable()
export class VendorCreditActivateBranches {
constructor(
@Inject(VendorCredit.name)
private readonly vendorCreditModel: TenantModelProxy<typeof VendorCredit>,
) {}

View File

@@ -0,0 +1,28 @@
import { IBranchesActivatedPayload } from '../../Branches.types';
import { events } from '@/common/events/events';
import { Injectable } from '@nestjs/common';
import { BillActivateBranches } from '../../integrations/Purchases/BillBranchesActivate';
import { OnEvent } from '@nestjs/event-emitter';
@Injectable()
export class BillBranchesActivateSubscriber {
constructor(
private readonly billActivateBranches: BillActivateBranches,
) { }
/**
* Updates bills transactions with the primary branch once
* the multi-branches is activated.
* @param {IBranchesActivatedPayload}
*/
@OnEvent(events.branch.onActivated)
async updateBillsWithBranchOnActivated({
primaryBranch,
trx,
}: IBranchesActivatedPayload) {
await this.billActivateBranches.updateBillsWithBranch(
primaryBranch.id,
trx,
);
}
}

View File

@@ -0,0 +1,28 @@
import { IBranchesActivatedPayload } from '../../Branches.types';
import { events } from '@/common/events/events';
import { Injectable } from '@nestjs/common';
import { VendorCreditActivateBranches } from '../../integrations/Purchases/VendorCreditBranchesActivate';
import { OnEvent } from '@nestjs/event-emitter';
@Injectable()
export class VendorCreditBranchesActivateSubscriber {
constructor(
private readonly vendorCreditActivateBranches: VendorCreditActivateBranches,
) { }
/**
* Updates vendor credits transactions with the primary branch once
* the multi-branches is activated.
* @param {IBranchesActivatedPayload}
*/
@OnEvent(events.branch.onActivated)
async updateVendorCreditsWithBranchOnActivated({
primaryBranch,
trx,
}: IBranchesActivatedPayload) {
await this.vendorCreditActivateBranches.updateVendorCreditsWithBranch(
primaryBranch.id,
trx,
);
}
}

View File

@@ -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',

View File

@@ -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',

View File

@@ -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',

View File

@@ -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()

View File

@@ -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 {}

View File

@@ -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;

View File

@@ -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() {}
}

View File

@@ -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',

View 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[];
}

View File

@@ -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,

View File

@@ -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],

View File

@@ -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);

View File

@@ -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 {}

View File

@@ -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 {}

View File

@@ -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 {}

View File

@@ -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();
};
}

View File

@@ -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 {}

View File

@@ -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();
// };
// }

View File

@@ -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,
);
}
}

View File

@@ -22,6 +22,7 @@ const providers = [
},
migrations: {
directory: configService.get('systemDatabase.migrationDir'),
loadExtensions: ['.js'],
},
seeds: {
directory: configService.get('systemDatabase.seedsDir'),

View File

@@ -33,6 +33,7 @@ export const TenancyDatabaseProxyProvider = ClsModule.forFeatureAsync({
},
migrations: {
directory: configService.get('tenantDatabase.migrationsDir'),
loadExtensions: ['.js'],
},
seeds: {
directory: configService.get('tenantDatabase.seedsDir'),

View File

@@ -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

View File

@@ -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",

View File

@@ -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);
}
`;

View File

@@ -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} />
) : (

View File

@@ -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' },
];

View File

@@ -1,5 +1,5 @@
// @ts-nocheck
export default {
export const App = {
"app_name": "BigCapital",
"app_version": "0.0.1 (build 12344)",
}

View File

@@ -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' },
];

View File

@@ -16,7 +16,7 @@ import {
VendorAction,
} from './abilityOption';
export default [
export const KeyboardShortcutsOptions = [
{
shortcut_key: 'Shift + I',
description: intl.get('jump_to_the_invoices'),

View File

@@ -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,

View File

@@ -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,

View File

@@ -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);

View File

@@ -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);

View File

@@ -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(() => {

View File

@@ -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'}

View File

@@ -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(() => {

View File

@@ -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'}

View File

@@ -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>

View File

@@ -44,6 +44,7 @@ function CustomerOpeningBalanceForm({
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
const formValues = {
...values,
opening_balance_at: moment(values.opening_balance_at).format('YYYY-MM-DD'),
};
// Handle request response success.

View File

@@ -45,6 +45,7 @@ function VendorOpeningBalanceForm({
const handleFormSubmit = (values, { setSubmitting, setErrors }) => {
const formValues = {
...values,
opening_balance_at: moment(values.opening_balance_at).format('YYYY-MM-DD'),
};
// Handle request response success.

View File

@@ -17,7 +17,7 @@ import {
FeatureCan,
InputPrependText,
} from '@/components';
import { FMoneyInputGroup, FFormGroup } from '@/components/Forms';
import { FMoneyInputGroup, FFormGroup, FDateInput } from '@/components/Forms';
import { useVendorOpeningBalanceContext } from './VendorOpeningBalanceFormProvider';
import { useSetPrimaryBranchToForm } from './utils';
@@ -59,26 +59,24 @@ function VendorOpeningBalanceFormFields({
</FFormGroup>
{/*------------ Opening balance at -----------*/}
<FastField name={'opening_balance_at'}>
{({ form, field: { value } }) => (
<FormGroup
label={<T id={'vendor_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={'vendor_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, vendor.currency_code)}>
{/*------------ Opening balance exchange rate -----------*/}
@@ -97,7 +95,8 @@ function VendorOpeningBalanceFormFields({
<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'}

View File

@@ -1,8 +1,20 @@
// @ts-nocheck
import React from 'react';
import { getColumnWidth } from '@/utils';
import * as R from 'ramda';
import { useGeneralLedgerContext } from './GeneralLedgerProvider';
import { Align } from '@/constants';
import { Align, CLASSES } from '@/constants';
/**
* Description cell wraps value in a div with muted text class.
*/
function DescriptionCell({ cell: { value } }) {
return React.createElement(
'div',
{ className: `cell ${CLASSES.TEXT_MUTED}` },
value,
);
}
const getTableCellValueAccessor = (index) => `cells[${index}].value`;
@@ -75,6 +87,16 @@ const transactionIdColumnAccessor = (column) => {
};
};
/**
* Description column accessor (muted text in wrapped cell).
*/
const descriptionColumnAccessor = (column) => {
return {
...column,
Cell: DescriptionCell,
};
};
const dynamiColumnMapper = R.curry((data, column) => {
const _numericColumnAccessor = numericColumnAccessor(data);
@@ -88,6 +110,7 @@ const dynamiColumnMapper = R.curry((data, column) => {
R.pathEq(['key'], 'reference_number'),
transactionIdColumnAccessor,
),
R.when(R.pathEq(['key'], 'description'), descriptionColumnAccessor),
R.when(R.pathEq(['key'], 'credit'), _numericColumnAccessor),
R.when(R.pathEq(['key'], 'debit'), _numericColumnAccessor),
R.when(R.pathEq(['key'], 'amount'), _numericColumnAccessor),

View File

@@ -1,9 +1,21 @@
// @ts-nocheck
import { Align } from '@/constants';
import React from 'react';
import { Align, CLASSES } from '@/constants';
import { getColumnWidth } from '@/utils';
import * as R from 'ramda';
import { useJournalSheetContext } from './JournalProvider';
/**
* Description cell wraps value in a div with muted text class.
*/
function DescriptionCell({ cell: { value } }) {
return React.createElement(
'span',
{ className: `cell ${CLASSES.TEXT_MUTED}` },
value,
);
}
const getTableCellValueAccessor = (index) => `cells[${index}].value`;
const getReportColWidth = (data, accessor, headerText) => {
@@ -86,6 +98,16 @@ const accountCodeColumnAccessor = (column) => {
};
};
/**
* Description column accessor (muted text in wrapped cell).
*/
const descriptionColumnAccessor = (column) => {
return {
...column,
Cell: DescriptionCell,
};
};
/**
* Dynamic column mapper.
* @param {} data -
@@ -105,6 +127,7 @@ const dynamicColumnMapper = R.curry((data, column) => {
R.pathEq(['key'], 'transaction_number'),
transactionNumberColumnAccessor,
),
R.when(R.pathEq(['key'], 'description'), descriptionColumnAccessor),
R.when(R.pathEq(['key'], 'account_code'), accountCodeColumnAccessor),
R.when(R.pathEq(['key'], 'credit'), _numericColumnAccessor),
R.when(R.pathEq(['key'], 'debit'), _numericColumnAccessor),
@@ -113,7 +136,7 @@ const dynamicColumnMapper = R.curry((data, column) => {
});
/**
* Composes the fetched dynamic columns from the server to the columns to pass it
* Composes the fetched dynamic columns from the server to the columns to pass it
* to the table component.
*/
export const dynamicColumns = (columns, data) => {

View File

@@ -14,6 +14,18 @@ import { useSettingsSelector } from '@/hooks/state';
import { transformItemFormData } from './ItemForm.schema';
import { useWatch } from '@/hooks/utils';
/**
* Error types for item operations.
*/
export const ItemErrorType = {
ItemNameExists: 'ITEM_NAME_EXISTS',
InventoryAccountCannotModified: 'INVENTORY_ACCOUNT_CANNOT_MODIFIED',
TypeCannotChangeWithItemHasTransactions: 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
ItemHasAssociatedTransactions: 'ITEM_HAS_ASSOCIATED_TRANSACTINS',
ItemHasAssociatedInventoryAdjustment: 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT',
ItemHasAssociatedTransactionsPlural: 'ITEM_HAS_ASSOCIATED_TRANSACTIONS',
} as const;
const defaultInitialValues = {
active: 1,
name: '',
@@ -74,7 +86,7 @@ export const transitionItemTypeKeyToLabel = (itemTypeKey) => {
// handle delete errors.
export const handleDeleteErrors = (errors) => {
if (
errors.find((error) => error.type === 'ITEM_HAS_ASSOCIATED_TRANSACTINS')
errors.find((error) => error.type === ItemErrorType.ItemHasAssociatedTransactions)
) {
AppToaster.show({
message: intl.get('the_item_has_associated_transactions'),
@@ -84,7 +96,7 @@ export const handleDeleteErrors = (errors) => {
if (
errors.find(
(error) => error.type === 'ITEM_HAS_ASSOCIATED_INVENTORY_ADJUSTMENT',
(error) => error.type === ItemErrorType.ItemHasAssociatedInventoryAdjustment,
)
) {
AppToaster.show({
@@ -96,7 +108,7 @@ export const handleDeleteErrors = (errors) => {
}
if (
errors.find(
(error) => error.type === 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
(error) => error.type === ItemErrorType.TypeCannotChangeWithItemHasTransactions,
)
) {
AppToaster.show({
@@ -107,7 +119,7 @@ export const handleDeleteErrors = (errors) => {
});
}
if (
errors.find((error) => error.type === 'ITEM_HAS_ASSOCIATED_TRANSACTIONS')
errors.find((error) => error.type === ItemErrorType.ItemHasAssociatedTransactionsPlural)
) {
AppToaster.show({
message: intl.get('item.error.you_could_not_delete_item_has_associated'),
@@ -214,10 +226,10 @@ export const transformSubmitRequestErrors = (error) => {
} = error;
const fields = {};
if (errors.find((e) => e.type === 'ITEM.NAME.ALREADY.EXISTS')) {
if (errors.find((e) => e.type === ItemErrorType.ItemNameExists)) {
fields.name = intl.get('the_name_used_before');
}
if (errors.find((e) => e.type === 'INVENTORY_ACCOUNT_CANNOT_MODIFIED')) {
if (errors.find((e) => e.type === ItemErrorType.InventoryAccountCannotModified)) {
AppToaster.show({
message: intl.get('cannot_change_item_inventory_account'),
intent: Intent.DANGER,
@@ -225,7 +237,7 @@ export const transformSubmitRequestErrors = (error) => {
}
if (
errors.find(
(e) => e.type === 'TYPE_CANNOT_CHANGE_WITH_ITEM_HAS_TRANSACTIONS',
(e) => e.type === ItemErrorType.TypeCannotChangeWithItemHasTransactions,
)
) {
AppToaster.show({

View File

@@ -87,7 +87,7 @@ export function WarehousesGridItemBox({
<WarehouseBoxRoot>
<WarehouseHeader>
<WarehouseTitle>
{title} {primary && <Icon icon={'star-18dp'} iconSize={16} />}
{title} {primary ? <Icon icon={'star-18dp'} iconSize={16} /> : null}
</WarehouseTitle>
<WarehouseCode>{code}</WarehouseCode>
<WarehouseIcon>
@@ -118,12 +118,21 @@ export const WarehousesList = styled.div`
`;
export const WarehouseBoxRoot = styled.div`
--x-box-border-color: #c8cad0;
--x-box-background-color: #fff;
--x-box-hover-border-color: #0153cc;
.bp4-dark & {
--x-box-border-color: rgba(255, 255, 255, 0.2);
--x-box-background-color: var(--color-dark-gray3);
--x-box-hover-border-color: #0153cc;
}
display: flex;
flex-direction: column;
flex-shrink: 0;
border-radius: 5px;
border: 1px solid #c8cad0;
background: #fff;
border: 1px solid var(--x-box-border-color);
background: var(--x-box-background-color);
margin: 5px 5px 8px;
width: 200px;
height: 160px;
@@ -132,7 +141,7 @@ export const WarehouseBoxRoot = styled.div`
position: relative;
&:hover {
border-color: #0153cc;
border-color: var(--x-box-hover-border-color);
}
`;
@@ -143,9 +152,16 @@ export const WarehouseHeader = styled.div`
`;
export const WarehouseTitle = styled.div`
--x-title-color: #000;
--x-title-icon-color: #e1b31d;
.bp4-dark & {
--x-title-color: var(--color-light-gray5);
--x-title-icon-color: #e1b31d;
}
font-size: 14px;
font-style: inherit;
color: #000;
color: var(--x-title-color);
white-space: nowrap;
font-weight: 500;
line-height: 1;
@@ -154,14 +170,19 @@ export const WarehouseTitle = styled.div`
margin: 0;
margin-left: 2px;
vertical-align: top;
color: #e1b31d;
color: var(--x-title-icon-color);
}
`;
const WarehouseCode = styled.div`
--x-code-color: #6b7176;
.bp4-dark & {
--x-code-color: var(--color-muted-text);
}
display: block;
font-size: 11px;
color: #6b7176;
color: var(--x-code-color);
margin-top: 4px;
`;
@@ -178,8 +199,13 @@ const WarehouseContent = styled.div`
`;
const WarehouseItem = styled.div`
--x-item-color: #000;
.bp4-dark & {
--x-item-color: var(--color-light-gray1);
}
font-size: 11px;
color: #000;
color: var(--x-item-color);
text-overflow: ellipsis;
overflow: hidden;

View File

@@ -1,6 +1,6 @@
// @ts-nocheck
import React from 'react';
import keyboardShortcuts from '@/constants/keyboardShortcutsOptions';
import { KeyboardShortcutsOptions } from '@/constants/keyboardShortcutsOptions';
import { useAbilitiesFilter } from '../utils/useAbilityContext';
/**
@@ -10,7 +10,7 @@ export const useKeywordShortcuts = () => {
const abilitiesFilter = useAbilitiesFilter();
return React.useMemo(
() => abilitiesFilter(keyboardShortcuts),
() => abilitiesFilter(KeyboardShortcutsOptions),
[abilitiesFilter],
);
};

View File

@@ -180,7 +180,7 @@ export function useEditCustomerOpeningBalance(props) {
return useMutation(
([id, values]) =>
apiRequest.post(`customers/${id}/opening_balance`, values),
apiRequest.put(`customers/${id}/opening-balance`, values),
{
onSuccess: (res, [id, values]) => {
// Invalidate specific customer.

View File

@@ -167,7 +167,7 @@ export function useEditVendorOpeningBalance(props) {
const apiRequest = useApiRequest();
return useMutation(
([id, values]) => apiRequest.post(`vendors/${id}/opening_balance`, values),
([id, values]) => apiRequest.put(`vendors/${id}/opening-balance`, values),
{
onSuccess: (res, [id, values]) => {
// Invalidate specific vendor.

View File

@@ -3,6 +3,7 @@ import { useQuery } from 'react-query';
import { castArray, defaultTo } from 'lodash';
import { useAuthOrganizationId } from './state';
import useApiRequest from './useRequest';
import { normalizeApiPath } from '../utils';
import { useRef } from 'react';
/**
@@ -19,7 +20,11 @@ export function useRequestQuery(query, axios, props) {
const states = useQuery(
query,
() => apiRequest.http({ ...axios, url: `/api/${axios.url}` }),
() =>
apiRequest.http({
...axios,
url: `/api/${normalizeApiPath(axios.url)}`,
}),
props,
);
// Momerize the default data.

View File

@@ -7,7 +7,7 @@ import {
useSetGlobalErrors,
useAuthToken,
} from './state';
import { getCookie } from '../utils';
import { getCookie, normalizeApiPath } from '../utils';
export default function useApiRequest() {
const setGlobalErrors = useSetGlobalErrors();
@@ -93,27 +93,27 @@ export default function useApiRequest() {
http,
get(resource, params) {
return http.get(`/api/${resource}`, params);
return http.get(`/api/${normalizeApiPath(resource)}`, params);
},
post(resource, params, config) {
return http.post(`/api/${resource}`, params, config);
return http.post(`/api/${normalizeApiPath(resource)}`, params, config);
},
update(resource, slug, params) {
return http.put(`/api/${resource}/${slug}`, params);
return http.put(`/api/${normalizeApiPath(resource)}/${slug}`, params);
},
put(resource, params) {
return http.put(`/api/${resource}`, params);
return http.put(`/api/${normalizeApiPath(resource)}`, params);
},
patch(resource, params, config) {
return http.patch(`/api/${resource}`, params, config);
return http.patch(`/api/${normalizeApiPath(resource)}`, params, config);
},
delete(resource, params) {
return http.delete(`/api/${resource}`, params);
return http.delete(`/api/${normalizeApiPath(resource)}`, params);
},
}),
[http],
@@ -130,22 +130,22 @@ export function useAuthApiRequest() {
() => ({
http,
get(resource, params) {
return http.get(`/api/${resource}`, params);
return http.get(`/api/${normalizeApiPath(resource)}`, params);
},
post(resource, params, config) {
return http.post(`/api/${resource}`, params, config);
return http.post(`/api/${normalizeApiPath(resource)}`, params, config);
},
update(resource, slug, params) {
return http.put(`/api/${resource}/${slug}`, params);
return http.put(`/api/${normalizeApiPath(resource)}/${slug}`, params);
},
put(resource, params) {
return http.put(`/api/${resource}`, params);
return http.put(`/api/${normalizeApiPath(resource)}`, params);
},
patch(resource, params, config) {
return http.patch(`/api/${resource}`, params, config);
return http.patch(`/api/${normalizeApiPath(resource)}`, params, config);
},
delete(resource, params) {
return http.delete(`/api/${resource}`, params);
return http.delete(`/api/${normalizeApiPath(resource)}`, params);
},
}),
[http],

View File

@@ -11,7 +11,10 @@ export const getPreferenceRoutes = () => [
},
{
path: `${BASE_URL}/branding`,
component: lazy(() => import('../containers/Preferences/Branding/PreferencesBrandingPage')),
component: lazy(
() =>
import('../containers/Preferences/Branding/PreferencesBrandingPage'),
),
exact: true,
},
{
@@ -29,14 +32,20 @@ export const getPreferenceRoutes = () => [
{
path: `${BASE_URL}/payment-methods`,
component: lazy(
() => import('../containers/Preferences/PaymentMethods/PreferencesPaymentMethodsPage'),
() =>
import(
'../containers/Preferences/PaymentMethods/PreferencesPaymentMethodsPage'
),
),
exact: true,
},
{
path: `${BASE_URL}/payment-methods/stripe/callback`,
component: lazy(
() => import('../containers/Preferences/PaymentMethods/PreferencesStripeCallback'),
() =>
import(
'../containers/Preferences/PaymentMethods/PreferencesStripeCallback'
),
),
exact: true,
},
@@ -112,16 +121,6 @@ export const getPreferenceRoutes = () => [
component: lazy(() => import('@/containers/Preferences/Item')),
exact: true,
},
// {
// path: `${BASE_URL}/sms-message`,
// component: SMSIntegration,
// exact: true,
// },
{
path: `${BASE_URL}/billing`,
component: lazy(() => import('@/containers/Subscriptions/BillingPage')),
exact: true,
},
{
path: `${BASE_URL}/api-keys`,
component: lazy(() => import('@/containers/Preferences/ApiKeys/ApiKeys')),

View File

@@ -196,7 +196,6 @@ $ns: bp4;
--color-preferences-sidebar-head-border: #bbcbd0;
--color-preferences-sidebar-head-text: #3b3b4c;
// Preferences - Topbar.
--color-preferences-topbar-background: #fff;
--color-preferences-topbar-border: #d2dde2;
@@ -209,7 +208,7 @@ $ns: bp4;
--color-financial-sheet-title-text: rgb(31, 50, 85);
--color-financial-sheet-type-text: rgb(31, 50, 85);
--color-financial-sheet-date-text: rgb(31, 50, 85);
--color-financial-sheet-footer-text: rgb(31, 50, 85);
--color-financial-sheet-footer-text: var(--color-muted-text);
--color-financial-sheet-minimal-title-text: #333;
// Transaction locking.
@@ -514,7 +513,7 @@ body.bp4-dark {
--color-financial-sheet-title-text: var(--color-light-gray1);
--color-financial-sheet-type-text: var(--color-light-gray1);
--color-financial-sheet-date-text: var(--color-light-gray1);
--color-financial-sheet-footer-text: var(--color-light-gray1);
--color-financial-sheet-footer-text: var(--color-muted-text);
--color-financial-sheet-minimal-title-text: var(--color-white);
// Transaction locking.

View File

@@ -365,7 +365,7 @@
border-bottom: 1px solid var(--color-datatable-constrant-head-border);
padding: 0.5rem;
}
.tbody .tr .td {
background: transparent;
padding: 0.5rem 0.5rem;
@@ -375,3 +375,32 @@
}
}
}
// Sticky header: blurred transparent background so body rows don't show through
.bigcapital-datatable.has-sticky,
.bigcapital-datatable.has-sticky-header {
&.table-constrant .table .thead .th,
&.table--constrant .table .thead .th {
background: rgba(255, 255, 255, 0.25);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
body.bp4-dark & {
background: rgba(28, 33, 39, 0.6);
}
}
}
// Sticky cells in table body: blurred transparent background so content doesn't show through
.bigcapital-datatable.has-sticky {
&.table-constrant .table .tbody .tr:not(:hover) .td[data-sticky-td],
&.table--constrant .table .tbody .tr:not(:hover) .td[data-sticky-td] {
background: rgba(255, 255, 255, 0.25);
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
body.bp4-dark & {
background: rgba(28, 33, 39, 0.6);
}
}
}

View File

@@ -13,6 +13,9 @@ import jsCookie from 'js-cookie';
import { deepMapKeys } from './map-key-deep';
export * from './deep';
/** Strips leading slash from a path segment to avoid double slashes when joining with a base (e.g. `/api/` + path). */
export const normalizeApiPath = (path) => (path || '').replace(/^\//, '');
export const getCookie = (name, defaultValue) =>
_.defaultTo(jsCookie.get(name), defaultValue);

213
pnpm-lock.yaml generated
View File

@@ -54,6 +54,15 @@ importers:
'@bigcapital/utils':
specifier: '*'
version: link:../../shared/bigcapital-utils
'@bull-board/api':
specifier: ^5.22.0
version: 5.23.0(@bull-board/ui@5.23.0)
'@bull-board/express':
specifier: ^5.22.0
version: 5.23.0
'@bull-board/nestjs':
specifier: ^5.22.0
version: 5.23.0(@bull-board/api@5.23.0)(@bull-board/express@5.23.0)(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@casl/ability':
specifier: ^5.4.3
version: 5.4.4
@@ -411,7 +420,7 @@ importers:
version: 0.3.7(@babel/core@7.28.5)(@blueprintjs/core@4.20.2)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)
'@blueprintjs-formik/datetime':
specifier: ^0.4.0
version: 0.4.0(@babel/core@7.28.5)(@blueprintjs-formik/core@0.3.7)(@blueprintjs/core@4.20.2)(@blueprintjs/datetime2@3.0.10)(@blueprintjs/datetime@4.4.37)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)
version: 0.4.0(@babel/core@7.28.5)(@blueprintjs-formik/core@0.3.7)(@blueprintjs/core@4.20.2)(@blueprintjs/datetime2@0.9.37)(@blueprintjs/datetime@4.4.37)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)
'@blueprintjs-formik/select':
specifier: ^0.4.5
version: 0.4.5(@blueprintjs/core@4.20.2)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react@18.3.1)
@@ -425,8 +434,8 @@ importers:
specifier: ^4.4.37
version: 4.4.37(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@blueprintjs/datetime2':
specifier: ^3.0.10
version: 3.0.10(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
specifier: ^0.9.0
version: 0.9.37(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@blueprintjs/popover2':
specifier: ^1.14.11
version: 1.14.11(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
@@ -4636,7 +4645,7 @@ packages:
- react-is
dev: false
/@blueprintjs-formik/datetime@0.4.0(@babel/core@7.28.5)(@blueprintjs-formik/core@0.3.7)(@blueprintjs/core@4.20.2)(@blueprintjs/datetime2@3.0.10)(@blueprintjs/datetime@4.4.37)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1):
/@blueprintjs-formik/datetime@0.4.0(@babel/core@7.28.5)(@blueprintjs-formik/core@0.3.7)(@blueprintjs/core@4.20.2)(@blueprintjs/datetime2@0.9.37)(@blueprintjs/datetime@4.4.37)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-51RW41DSM96seIgOGz2gw2DX1SfH8+Ydi9JBCNmLaFZRqyKZgRX+qpxmltgbS97lk2IOfCEyKSxoGGfz4XlnPg==}
peerDependencies:
'@blueprintjs-formik/core': ^0.3.2 || 4 || 5
@@ -4651,7 +4660,7 @@ packages:
'@blueprintjs-formik/core': 0.3.7(@babel/core@7.28.5)(@blueprintjs/core@4.20.2)(@blueprintjs/select@4.9.24)(formik@2.4.6)(react-dom@18.3.1)(react-is@18.3.1)(react@18.3.1)
'@blueprintjs/core': 4.20.2(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@blueprintjs/datetime': 4.4.37(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@blueprintjs/datetime2': 3.0.10(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@blueprintjs/datetime2': 0.9.37(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@blueprintjs/select': 4.9.24(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
formik: 2.4.6(react@18.3.1)
lodash.get: 4.4.2
@@ -4694,12 +4703,6 @@ packages:
tslib: 2.5.3
dev: false
/@blueprintjs/colors@5.1.12:
resolution: {integrity: sha512-7GQWUQ82eLE1te++DC8fRO2B31bsSwia82NLamZfKgjHY9V4zxafMT1DK5gKlmmy0nCjpdcCc+df4aVZMHGLww==}
dependencies:
tslib: 2.6.2
dev: false
/@blueprintjs/core@4.20.2(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-5v4Nr0jozfAjiOkjY4zvt1XSpt4ldnrSaxtwo506S2cxJYfwFeMTmDshXNPFcc8L1fjZMxi0IWI2WABXzZXS6w==}
hasBin: true
@@ -4727,51 +4730,28 @@ packages:
tslib: 2.5.3
dev: false
/@blueprintjs/core@6.4.1(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-JSA8seGiZ2dpmugO7Cea4bh0XEkTioDyzM1Z/0piFsziVv4OeV3jrmMXMegtCdHqpwpWYj8reGuPzFKHGemXXw==}
hasBin: true
/@blueprintjs/datetime2@0.9.37(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-eCoQbQK1IO5uegmYugj/K5helj6oA7s/4ZkA7dweBHBHXQ3KmZLh2Zj+O0ESOq/qqvlvCfiNCfsObk309EIbKQ==}
peerDependencies:
'@types/react': '18'
react: '18'
react-dom: '18'
'@types/react': ^16.14.32 || 17
react: ^16.8 || 17
react-dom: ^16.8 || 17
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@blueprintjs/colors': 5.1.12
'@blueprintjs/icons': 6.3.2(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@popperjs/core': 2.11.8
'@blueprintjs/core': 4.20.2(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@blueprintjs/datetime': 4.4.37(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@blueprintjs/popover2': 1.14.11(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@blueprintjs/select': 4.9.24(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@types/react': 18.3.4
classnames: 2.5.1
normalize.css: 8.0.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
react-popper: 2.3.0(@popperjs/core@2.11.8)(react-dom@18.3.1)(react@18.3.1)
react-transition-group: 4.4.5(react-dom@18.3.1)(react@18.3.1)
tslib: 2.6.2
use-sync-external-store: 1.6.0(react@18.3.1)
dev: false
/@blueprintjs/datetime2@3.0.10(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-pli0hkv93aH2Ou3inZ2KIdNBLj5dYW8XVu21Rr3fSoh5b9aUsThAVDmb51LjlfNiPVi1i+y2omGuDRTKV+zUgw==}
peerDependencies:
'@types/react': '18'
react: '18'
react-dom: '18'
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@blueprintjs/colors': 5.1.12
'@blueprintjs/core': 6.4.1(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@blueprintjs/datetime': 6.0.10(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@blueprintjs/icons': 6.3.2(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@types/react': 18.3.4
date-fns: 2.30.0
date-fns-tz: 1.3.8(date-fns@2.30.0)
lodash: 4.17.21
react: 18.3.1
react-day-picker: 8.10.1(date-fns@2.30.0)(react@18.3.1)
react-dom: 18.3.1(react@18.3.1)
tslib: 2.6.2
tslib: 2.5.3
dev: false
/@blueprintjs/datetime@4.4.37(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1):
@@ -4793,30 +4773,6 @@ packages:
tslib: 2.5.3
dev: false
/@blueprintjs/datetime@6.0.10(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-8i8P12OwCe0JGTkX20EtHvembEjwfqkf1pQcec3lMdPDDeEpPmV3vzPWxwCS+NSc6vUGl/2aAFs+Ro7qDW1msw==}
peerDependencies:
'@types/react': '18'
react: '18'
react-dom: '18'
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@blueprintjs/core': 6.4.1(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@blueprintjs/icons': 6.3.2(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@blueprintjs/select': 6.0.10(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@types/react': 18.3.4
classnames: 2.5.1
date-fns: 2.30.0
date-fns-tz: 2.0.1(date-fns@2.30.0)
react: 18.3.1
react-day-picker: 8.10.1(date-fns@2.30.0)(react@18.3.1)
react-dom: 18.3.1(react@18.3.1)
react-innertext: 1.1.5(@types/react@18.3.4)(react@18.3.1)
tslib: 2.6.2
dev: false
/@blueprintjs/icons@4.16.0:
resolution: {integrity: sha512-cyfgjUZcZCtQrXWUV8FwqYTFEzduV4a0N7yhOU38jY+cBRCLu/sDrD0Osvfk4DGRvNe4YjY7pohVLFSxpg68Uw==}
dependencies:
@@ -4825,24 +4781,6 @@ packages:
tslib: 2.5.3
dev: false
/@blueprintjs/icons@6.3.2(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-rOdSg7yPmjtqy3e+wD66tXCrzt57M59htVOikwunTaCyQ61+bsF0PyqLfNlhsw1mXqZm8uxSINs1bhnwXhQCiw==}
peerDependencies:
'@types/react': '18'
react: '18'
react-dom: '18'
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@types/react': 18.3.4
change-case: 4.1.2
classnames: 2.5.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
tslib: 2.6.2
dev: false
/@blueprintjs/popover2@1.14.11(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-5XAjeb2mlWjYXC0pqrNDLzHSsX85Zaiv8jixxUN9abarMUUFKGATgGF8MRsWTLAW94Gli6CB1lzVkrYkRHHf6Q==}
peerDependencies:
@@ -4884,26 +4822,6 @@ packages:
tslib: 2.5.3
dev: false
/@blueprintjs/select@6.0.10(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-RjDuomn4KhluiN7tVofLLX6S+b+un0mLehvmOSETpqk1C8lHXCxReIXxFiWqTB+YiIgkqQ6aRNjQs35cJBbrfg==}
peerDependencies:
'@types/react': '18'
react: '18'
react-dom: '18'
peerDependenciesMeta:
'@types/react':
optional: true
dependencies:
'@blueprintjs/colors': 5.1.12
'@blueprintjs/core': 6.4.1(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@blueprintjs/icons': 6.3.2(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1)
'@types/react': 18.3.4
classnames: 2.5.1
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
tslib: 2.6.2
dev: false
/@blueprintjs/table@4.10.12(@types/react@18.3.4)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-f5NbjuHQ/1vb5hkT+WsCnjIU3Vp2OCWdyDUopVmgj4qHIgzgX38vP2sLS8aUFlbsEQr/xAU9mfkpp4FT+65mXA==}
peerDependencies:
@@ -4946,6 +4864,51 @@ packages:
tslib: 2.5.3
dev: false
/@bull-board/api@5.23.0(@bull-board/ui@5.23.0):
resolution: {integrity: sha512-ZZGsWJ+XBG49GAlNgAL9tTEV6Ms7gMkQnZDbzwUhjGChCKWy62RWuPoZSefNXau9QH9+QzlzHRUeFvt4xr5wiw==}
peerDependencies:
'@bull-board/ui': 5.23.0
dependencies:
'@bull-board/ui': 5.23.0
redis-info: 3.1.0
dev: false
/@bull-board/express@5.23.0:
resolution: {integrity: sha512-t/mHzJMlZBtSKD8v81kbZoexOmtQxKVnHZfHJ0um5vrkHNJJuzKuwbR+n9nf1u89AGdyXoWxqDhBDslxv3zrrQ==}
dependencies:
'@bull-board/api': 5.23.0(@bull-board/ui@5.23.0)
'@bull-board/ui': 5.23.0
ejs: 3.1.10
express: 4.21.1
transitivePeerDependencies:
- supports-color
dev: false
/@bull-board/nestjs@5.23.0(@bull-board/api@5.23.0)(@bull-board/express@5.23.0)(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1):
resolution: {integrity: sha512-eRJCz6PqKzrlZfd7akc+6/1eXwNO7Fp468TmSo8avlPcKhStZH3fUmCF6xxfRQpm9kn/eDOp+H18CTnSafmwmw==}
peerDependencies:
'@bull-board/api': ^5.23.0
'@bull-board/express': ^5.23.0
'@nestjs/common': ^9.0.0 || ^10.0.0
'@nestjs/core': ^9.0.0 || ^10.0.0
reflect-metadata: ^0.1.13 || ^0.2.0
rxjs: ^7.8.1
dependencies:
'@bull-board/api': 5.23.0(@bull-board/ui@5.23.0)
'@bull-board/express': 5.23.0
'@nestjs/bull-shared': 10.2.2(@nestjs/common@10.4.7)(@nestjs/core@10.4.7)
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7)(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.1)
reflect-metadata: 0.2.2
rxjs: 7.8.1
dev: false
/@bull-board/ui@5.23.0:
resolution: {integrity: sha512-iI/Ssl8T5ZEn9s899Qz67m92M6RU8thf/aqD7cUHB2yHmkCjqbw7s7NaODTsyArAsnyu7DGJMWm7EhbfFXDNgQ==}
dependencies:
'@bull-board/api': 5.23.0(@bull-board/ui@5.23.0)
dev: false
/@casl/ability@5.4.4:
resolution: {integrity: sha512-7+GOnMUq6q4fqtDDesymBXTS9LSDVezYhFiSJ8Rn3f0aQLeRm7qHn66KWbej4niCOvm0XzNj9jzpkK0yz6hUww==}
dependencies:
@@ -15031,10 +14994,10 @@ packages:
is-data-view: 1.0.1
dev: true
/date-fns-tz@2.0.1(date-fns@2.30.0):
resolution: {integrity: sha512-fJCG3Pwx8HUoLhkepdsP7Z5RsucUi+ZBOxyM5d0ZZ6c4SdYustq0VMmOu6Wf7bli+yS/Jwp91TOCqn9jMcVrUA==}
/date-fns-tz@1.3.8(date-fns@2.30.0):
resolution: {integrity: sha512-qwNXUFtMHTTU6CFSFjoJ80W8Fzzp24LntbjFFBgL/faqds4e5mo9mftoRLgr3Vi1trISsg4awSpYVsOQCRnapQ==}
peerDependencies:
date-fns: 2.x
date-fns: '>=2.0.0'
dependencies:
date-fns: 2.30.0
dev: false
@@ -15557,7 +15520,6 @@ packages:
hasBin: true
dependencies:
jake: 10.9.1
dev: true
/electron-to-chromium@1.4.782:
resolution: {integrity: sha512-JUfU61e8tr+i5Y1FKXcKs+Xe+rJ+CEqm4cgv1kMihPE2EvYHmYyVr3Im/+1+Z5B29Be2EEGCZCwAc6Tazdl1Yg==}
@@ -16832,7 +16794,6 @@ packages:
resolution: {integrity: sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==}
dependencies:
minimatch: 5.1.6
dev: true
/fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
@@ -18572,7 +18533,6 @@ packages:
chalk: 4.1.2
filelist: 1.0.4
minimatch: 3.1.2
dev: true
/javascript-natural-sort@0.7.1:
resolution: {integrity: sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==}
@@ -20133,7 +20093,6 @@ packages:
engines: {node: '>=10'}
dependencies:
brace-expansion: 2.0.1
dev: true
/minimatch@8.0.4:
resolution: {integrity: sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA==}
@@ -22488,16 +22447,6 @@ packages:
react: 18.3.1
dev: false
/react-day-picker@8.10.1(date-fns@2.30.0)(react@18.3.1):
resolution: {integrity: sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==}
peerDependencies:
date-fns: ^2.28.0 || ^3.0.0
react: ^16.8.0 || ^17.0.0 || ^18.0.0
dependencies:
date-fns: 2.30.0
react: 18.3.1
dev: false
/react-docgen-typescript@2.2.2(typescript@5.6.3):
resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==}
peerDependencies:
@@ -23273,6 +23222,12 @@ packages:
engines: {node: '>=4'}
dev: false
/redis-info@3.1.0:
resolution: {integrity: sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg==}
dependencies:
lodash: 4.17.21
dev: false
/redis-parser@3.0.0:
resolution: {integrity: sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==}
engines: {node: '>=4'}
@@ -25786,14 +25741,6 @@ packages:
tslib: 2.8.1
dev: true
/use-sync-external-store@1.6.0(react@18.3.1):
resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
dependencies:
react: 18.3.1
dev: false
/util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}

View File

@@ -2,7 +2,6 @@
# Initialize the essential variables.
BRANCH=main
CURRENT=$PWD
BIGCAPITAL_INSTALL_DIR=$PWD/bigcapital-$(echo $BRANCH | sed -r 's@(\/|" "|\.)@-@g')
BIGCAPITAL_CLONE_TEMP_DIR=$(mktemp -d)
CPU_ARCH=$(uname -m)
@@ -20,8 +19,6 @@ else
COMPOSE_CMD="docker compose"
fi
REPO=https://github.com/bigcapitalhq/bigcapital
# Prints the Bigcapital logo once running the script.
function print_logo() {
clear
@@ -142,8 +139,8 @@ function askForAction() {
}
function install() {
echo "Installing Bigcaoital.........."
echo "installing is going to take few mintues..."
echo "Installing Bigcapital.........."
echo "installing is going to take few minutes..."
download
setup_env
}
@@ -203,10 +200,11 @@ function startServices() {
done
printf "\r\033[K"
echo " API server started successfully ✅"
source "${DOCKER_ENV_PATH}"
ACCESS_URL=$(grep -E '^BASE_URL=' "$DOCKER_ENV_PATH" 2>/dev/null | cut -d= -f2-)
[ -z "$ACCESS_URL" ] && ACCESS_URL="http://localhost"
echo " Bigcapital server started successfully ✅"
echo ""
echo " You can access the application at $WEB_URL"
echo " You can access the application at $ACCESS_URL"
echo ""
}
@@ -223,20 +221,19 @@ function restartServices() {
}
function viewLogs(){
ARG_SERVICE_NAME=$2
echo
echo "Select a Service you want to view the logs for:"
echo " 1) Webapp"
echo " 2) API"
echo " 3) Migration"
echo " 4) Nginx Proxy"
echo " 4) Envoy Proxy"
echo " 5) MariaDB"
echo " 0) Back to Main Menu"
echo
read -p "Service: " DOCKER_SERVICE_NAME
until (( DOCKER_SERVICE_NAME >= 0 && DOCKER_SERVICE_NAME <= 5 )); do
echo "Invalid selection. Please enter a number between 1 and 11."
echo "Invalid selection. Please enter a number between 0 and 5."
read -p "Service: " DOCKER_SERVICE_NAME
done
@@ -248,7 +245,7 @@ function viewLogs(){
1) viewSpecificLogs "webapp";;
2) viewSpecificLogs "server";;
3) viewSpecificLogs "database_migration";;
4) viewSpecificLogs "nginx";;
4) viewSpecificLogs "proxy";;
5) viewSpecificLogs "mysql";;
0) askForAction;;
*) echo "INVALID SERVICE NAME SUPPLIED";;